patterns.module in Patterns 5
Same filename and directory in other branches
Enables extremely simple adding/removing features to your site with minimal to no configuration
File
patterns.moduleView source
<?php
/**
* @file
* Enables extremely simple adding/removing features to your site with minimal to no configuration
*/
/**
* @todo:
* @ Enable pattern configurations
* @ **done**Enable actions to see ids created/updated from other actions inside the pattern (tokens?)
* @ **semi-done** Reset patterns
* @ Enable components to analyze the current pattern for better validation
* @ Allow module version restricting
* @ Put in functionality to auto-download modules (and the correct version)
* @ Enable backups before running patterns and reverting back to those backups
* @ Implement a progress meter
* @ Handle default values better to allow for absolute minimal actions
* @ Enable interactive actions by allowing patterns to resume from a saved position
* @ Implement an export feature for all available form_ids
* @ Allow resuming failed patterns
* @ In the pattern details, list any sub-patterns directly on the patterns listing page
*/
/**
* Implementation of hook_perm().
*/
function patterns_perm() {
return array(
'administer patterns',
);
}
/**
* Implementation of hook_menu().
*/
function patterns_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array(
'path' => 'admin/build/patterns',
'description' => t('Administer patterns available for your site'),
'access' => user_access('administer patterns'),
'title' => t('Patterns'),
'callback' => 'patterns_list',
'type' => MENU_NORMAL_ITEM,
);
$items[] = array(
'path' => 'admin/build/patterns/list',
'title' => t('List'),
'callback' => 'patterns_list',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
// $items[] = array('path' => 'admin/build/patterns/settings',
// 'title' => t('Settings'),
// 'callback' => 'drupal_get_form',
// 'callback arguments' => array('patterns_settings'),
// 'type' => MENU_LOCAL_TASK,
// 'weight' => 10
// );
$items[] = array(
'path' => 'admin/build/patterns/configure',
'title' => t('Configure Pattern'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'patterns_configure_pattern',
),
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => 'admin/build/patterns/edit',
'title' => t('Edit Pattern'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'patterns_edit',
),
'type' => MENU_CALLBACK,
);
// $items[] = array('path' => 'admin/build/patterns/info',
// 'title' => t('Pattern Details'),
// 'callback' => 'patterns_info',
// 'type' => MENU_CALLBACK
// );
$items[] = array(
'path' => 'admin/build/patterns/enable',
'access' => user_access('administer patterns'),
'title' => t('Enable Pattern'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'patterns_enable_pattern',
),
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => 'admin/build/patterns/disable',
'access' => user_access('administer patterns'),
'title' => t('Disable Pattern'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'patterns_disable_pattern',
),
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => 'admin/build/patterns/modules',
'access' => user_access('administer patterns'),
'title' => t('Pattern Modules'),
'callback' => 'patterns_modules_page',
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => 'admin/build/patterns/revert',
'access' => user_access('administer patterns'),
'title' => t('Revert Pattern'),
'callback' => 'patterns_revert',
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => 'admin/build/patterns/import',
'access' => user_access('administer patterns'),
'title' => t('Import'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'patterns_import_source',
),
'type' => MENU_LOCAL_TASK,
);
$items[] = array(
'path' => 'admin/build/patterns/import/xmltext',
'access' => user_access('administer patterns'),
'title' => t('Import via XML Source'),
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items[] = array(
'path' => 'admin/build/patterns/import/xmlfile',
'access' => user_access('administer patterns'),
'title' => t('Import via XML File'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'patterns_import_file',
),
'type' => MENU_LOCAL_TASK,
);
$items[] = array(
'path' => 'admin/build/patterns/import/xmlurl',
'access' => user_access('administer patterns'),
'title' => t('Import via XML URL'),
'callback' => 'drupal_get_form',
'callback arguments' => array(
'patterns_import_url',
),
'type' => MENU_LOCAL_TASK,
);
// $items[] = array('path' => 'admin/build/patterns/import/server',
// 'access' => user_access('administer patterns'),
// 'title' => t('Import via Patterns Server'),
// 'callback' => 'drupal_get_form',
// 'callback arguments' => array('patterns_import_server'),
// 'type' => MENU_LOCAL_TASK
// );
}
return $items;
}
/**
* Display the import pattern form
*/
function patterns_import_source() {
$form['xmlname'] = array(
'#type' => 'textfield',
'#title' => t('Pattern Identifier'),
'#description' => t('Machine readable name for the pattern. The actual title should be included in the pattern itself.'),
'#required' => true,
);
$form['xmlsource'] = array(
'#type' => 'textarea',
'#rows' => 15,
'#title' => t('Enter Pattern XML'),
'#description' => t('Imported patterns are not executed until you run them manually. You can leave off the <?xml> tag at the top.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
$form['#base'] = 'patterns_import';
return $form;
}
/**
* Display the pattern settings form
*/
// function patterns_settings() {
// drupal_set_message('This feature not implemented yet.');
// }
/**
* Display the import pattern from server form
*/
// function patterns_import_server() {
// drupal_set_message('This feature not implemented yet.');
// }
/**
* Display the import pattern file form
*/
function patterns_import_file() {
$form['#attributes']['enctype'] = 'multipart/form-data';
$form['xmlfile'] = array(
'#type' => 'file',
'#title' => t('Upload XML Pattern File'),
'#description' => t('Imported patterns are not executed until you run them manually.'),
'#size' => 48,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
$form['#base'] = 'patterns_import';
return $form;
}
/**
* Display the import pattern url form
*/
function patterns_import_url() {
$form['xmlurl'] = array(
'#type' => 'textfield',
'#title' => t('Specify an XML url'),
'#description' => t('Import a pattern from a remote URL. Imported patterns are not executed until you run them manually.'),
'#size' => 48,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
$form['#base'] = 'patterns_import';
return $form;
}
function patterns_import_validate($form_id, $values) {
global $form_values;
if ($file = file_check_upload('xmlfile')) {
$form_values['xmlsource'] = file_get_contents($file->filepath);
}
else {
if (isset($values['xmlfile'])) {
form_set_error('xmlfile', t('Error uploading XML file.'));
}
else {
if ($values['xmlurl']) {
if (!ini_get('allow_url_fopen')) {
form_set_error('xmlsource', t('allow_url_fopen must be enabled in your php configuration in order to use this feature.'));
return;
}
if (!($form_values['xmlsource'] = file_get_contents($values['xmlurl']))) {
form_set_error('xmlsource', t('Failed to retreive the pattern specified.'));
return;
}
$form_values['xmlname'] = preg_replace('/\\.[^\\.]*$/', '', basename($form_values['xmlurl']));
}
}
}
if (strpos($form_values['xmlsource'], '<?xml') !== 0) {
$form_values['xmlsource'] = '<?xml version="1.0" encoding="ISO-8859-1"?>' . $form_values['xmlsource'];
}
if ($form_values['xmlname'] && preg_match('/[^a-zA-Z0-9_]/', $form_values['xmlname'])) {
form_set_error('xmlname', t('You can only include letters, numbers, and underscores in the pattern identifier.'));
}
else {
if ($form_values['xmlname'] && preg_match('/^_/', $form_values['xmlname'])) {
form_set_error('xmlname', t('You cannot start the pattern identifier with an underscore.'));
}
}
$parse = xml_parser_create();
xml_parse_into_struct($parse, $form_values['xmlsource'], $vals, $index);
// Check that the xml was properly parsed and also that the
// root <pattern> tag and also an <info> tag were used.
if (!$vals || $vals[0]['tag'] != 'PATTERN' || $vals[1]['tag'] != 'INFO') {
form_set_error('xmlsource', t('Error parsing the XML, please check your syntax and try again.'));
}
}
function patterns_import_submit($form_id, $form_values) {
if ($file = file_check_upload('xmlfile')) {
// Currently nothing is happening with files saved here....
if (file_check_directory(file_create_path(variable_get('patterns_save_xml', 'patterns')))) {
file_save_upload('xmlfile', variable_get('patterns_save_xml', 'patterns'));
}
else {
drupal_set_message(t('Pattern was registered but the file was not saved on the server because of improperly setup files directory.'));
}
}
else {
if ($form_values['xmlsource']) {
file_save_data($form_values['xmlsource'], variable_get('patterns_save_xml', 'patterns') . '/' . $form_values['xmlname'] . '.xml', FILE_EXISTS_REPLACE);
}
}
// Reload patterns
patterns_get_patterns(true);
return 'admin/build/patterns';
}
function patterns_list() {
drupal_add_css(drupal_get_path('module', 'patterns') . '/patterns.css');
drupal_add_js(drupal_get_path('module', 'patterns') . '/patterns.js');
patterns_load();
$patterns = patterns_get_patterns();
if (empty($patterns)) {
return t('No patterns available.');
}
// $header = array(t('Title'), t('Status'), t('Version'), t('Public'), t('Actions'));
$header = array(
t('Title'),
t('Status'),
t('Version'),
t('Actions'),
);
// List all patterns
$rows = array();
foreach ($patterns as $pid => $pattern) {
$actions = array();
if (!$pattern->status) {
$actions[] = l(t('Run'), 'admin/build/patterns/enable/' . $pid);
}
else {
if ($pattern->enabled >= $pattern->updated) {
$actions[] = l(t('Re-Run'), 'admin/build/patterns/enable/' . $pid);
}
else {
$actions[] = l(t('Run Update'), 'admin/build/patterns/enable/' . $pid);
}
}
$actions[] = l(t('Edit'), 'admin/build/patterns/edit/' . $pid);
$actions = implode(' ', $actions);
$cells = array();
// $title = l($pattern->title, 'admin/build/patterns/info/'. $pid, array('class' => 'pattern-title', 'id' => 'pid-'. $pid));
$title = '<span id="pid-' . $pid . '" class="pattern-title">' . $pattern->title . '</span>';
// $view_more = '<div>'. t('Clik on pattern title to see more details.') .'</div>';
$info = array();
$info[] = t('Author: ') . $pattern->info['author'];
$info[] = t('Email: ') . $pattern->info['author_email'];
$info[] = t('Web: ') . $pattern->info['author_website'];
$author = theme('item_list', $info);
$title .= '<div id="pid-' . $pid . '-info" class="pattern-info">' . $author . $pattern->description . $view_more . '</div>';
$cells[] = $title;
$cells[] = $pattern->status ? t('Enabled') : t('Disabled');
$cells[] = $pattern->info['version'];
// $cells[] = $pattern->public ? t('Yes') : t('No');
$cells[] = $actions;
$category = $pattern->info['category'] ? $pattern->info['category'] : t('Other');
$rows[$category][] = $cells;
}
ksort($rows);
foreach ($rows as $title => $category) {
$fieldset = array(
'#title' => t($title),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#value' => theme('table', $header, $category),
);
$output .= theme('fieldset', $fieldset);
}
return $output;
}
/**
* Menu callback to undo a patterns update changes
*/
function patterns_revert($pid) {
if ($name = db_result(db_query('SELECT name FROM {patterns} WHERE pid = "%d"', $pid))) {
$path = file_create_path(variable_get('patterns_save_xml', 'patterns') . '/enabled/' . $name . '.xml');
$new = db_result(db_query('SELECT file FROM {patterns} WHERE pid = "%d"'));
}
else {
drupal_set_message(t('The pattern you specified does not exist.'), 'error');
drupal_goto('admin/build/patterns');
}
if (file_exists($path)) {
if (file_move($path, $new, FILE_EXISTS_REPLACE)) {
drupal_set_message(t('This pattern was reverted to the state it was at when it was enabled.'));
drupal_goto();
}
}
else {
drupal_set_message(t('The patterns enabled-state was not saved properly, therefore it cannot be reverted.'), 'error');
}
drupal_goto('admin/build/patterns');
}
/**
* Menu callback to display patterns details page
*/
// function patterns_info($pid = null) {
// if (!is_numeric($pid)) {
// drupal_set_message(t('You must specify a pattern.'));
// return;
// }
//
// $pattern = patterns_get_pattern($pid);
//
// $output = '';
// return $output;
// }
/**
* Menu callback to edit a patterns data
*/
function patterns_edit($pid = null) {
if (!is_numeric($pid)) {
drupal_set_message(t('You must specify a pattern to edit.'));
return;
}
$pattern = patterns_get_pattern($pid);
// TODO: Turn php into xml here.
// For now just allow modifying the original xml, which
// means the modification cannot be further modified
if (!$pattern->file) {
drupal_set_message(t('This pattern does not seem to have an XML source file to base the modifications off of.'), 'error');
return;
}
$xml = file_get_contents($pattern->file);
$form['name'] = array(
'#type' => 'value',
'#value' => $pattern->name,
);
$form['pid'] = array(
'#type' => 'value',
'#value' => $pattern->pid,
);
if ($pattern->enabled <= $pattern->updated) {
$form['revert'] = array(
'#type' => 'markup',
'#value' => l(t('Undo update changes to the state when you enabled the pattern.'), 'admin/build/patterns/revert/' . $pid, array(), drupal_get_destination()),
);
}
$form['xml'] = array(
'#type' => 'textarea',
'#title' => t('Pattern\'s XML'),
'#rows' => 25,
'#default_value' => $xml,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Validate pattern modifications (make sure proper XML)
*/
function patterns_edit_validate($form_id, $form_values) {
// Do validations....
$path = file_create_path(variable_get('patterns_save_xml', 'patterns'));
if (!file_check_directory($path, true)) {
form_set_error('form_token', t('Unable to create @path to save the new pattern to.', array(
'@path' => $path,
)));
}
}
/**
* Submit edits to the pattern
*/
function patterns_edit_submit($form_id, $form_values) {
// If this is an enabled pattern, make sure the enabled pattern is saved in its current state
if ($file = db_result(db_query('SELECT file FROM {patterns} WHERE status = 1 AND name = "%s"', $form_values['name']))) {
$dir = file_directory_path() . '/' . variable_get('patterns_save_xml', 'patterns') . '/enabled';
file_check_directory($dir, true);
$path = $dir . '/' . $form_values['name'] . '.xml';
if (!file_exists($path)) {
file_copy($file, $path, FILE_EXISTS_ERROR);
}
}
// Save the new pattern into the pattern files dir.
$path = file_directory_path() . '/' . variable_get('patterns_save_xml', 'patterns') . '/' . $form_values['name'] . '.xml';
file_save_data($form_values['xml'], $path, FILE_EXISTS_REPLACE);
$old = db_result(db_query('SELECT file FROM {patterns} WHERE name = "%s"', $form_values['name']));
// Load and save pattern
if ($pattern = patterns_load_xml($path)) {
if ($old) {
db_query('UPDATE {patterns} SET file = "%s", updated = "%s" WHERE pid = "%d"', $path, time(), $form_values['pid']);
}
patterns_save_pattern($pattern, $path);
}
drupal_set_message(t('%name was saved.', array(
'%name' => $form_values['name'],
)));
return 'admin/build/patterns';
}
/**
* List the modules used by a particular pattern
*/
function patterns_modules_page($pid) {
$pattern = patterns_get_pattern($pid);
drupal_set_title($pattern->title . ' ' . t('(Pattern Modules)'));
$modules = patterns_modules($pattern);
$modules_info = module_rebuild_cache();
$modules_list = module_list();
// Get module name, whether its to be disabled or enabled,
// whether the module is available or not, and whether it is
// currently enabled or not
foreach ($modules as $module) {
$row = array();
$delete = is_array($module) ? $module['delete'] : false;
$module = is_array($module) ? $module['value'] : $module;
$available = array_key_exists($module, $modules_info);
$enabled = array_key_exists($module, $modules_list);
$row[] = $module;
$row[] = $available ? t('Yes') : '<span class="alert">' . t('No') . '</span>';
$row[] = $enabled ? t('Yes') : '<span class="alert">' . t('No') . '</span>';
$row[] = $delete ? t('Delete') : t('Enable');
$rows[] = $row;
if (!$available) {
$not_available = true;
}
}
if ($not_available) {
drupal_set_message(t('Some modules are not availalble, please download them before running this pattern.'), 'error');
}
else {
drupal_set_message(t('All modules required by this module are available. Click !here to run this pattern.', array(
'!here' => l(t('here'), 'admin/build/patterns/enable/' . $pid),
)));
}
return theme('table', array(
t('Name'),
t('Available'),
t('Enabled'),
t('Pattern Action'),
), $rows, t('Modules used for this pattern'));
}
function patterns_load() {
static $loaded = false;
if ($loaded) {
return;
}
require_once drupal_get_path('module', 'patterns') . '/patterns.inc';
foreach (file_scan_directory(drupal_get_path('module', 'patterns') . '/components', '.\\.inc') as $file) {
include_once $file->filename;
}
$loaded = true;
}
function patterns_get_patterns($reset = true) {
patterns_load();
if ($reset || !variable_get('patterns_loaded', false)) {
// Get a listing of enabled patterns
$enabled = array();
$result = db_query('SELECT file FROM {patterns} WHERE status = 1');
while ($pattern = db_fetch_object($result)) {
$enabled[] = $result->file;
}
$path = file_create_path(variable_get('patterns_save_xml', 'patterns'));
$priority = array();
// Get uploaded patterns first
if (file_check_directory($path)) {
// Check if .htaccess file exists in path, if not insert it
_patterns_check_file_dir();
foreach (file_scan_directory($path, '.\\.xml') as $file) {
// Don't save enabled pattern backups
if (strpos($file->filename, $path . '/enabled/') === 0) {
continue;
}
// Set priority so these patterns won't get over-written
$priority[] = $file->name;
// Can't update existing patterns that are enabled
if (in_array($file->filename, $enabled)) {
continue;
}
// Load and save pattern
if ($pattern = patterns_load_xml($file->filename)) {
patterns_save_pattern($pattern, $file->filename);
}
}
}
// Get per-site patterns next
foreach (file_scan_directory(conf_path() . '/patterns', '.\\.xml') as $file) {
// Can't update existing patterns that are enabled
if (in_array($file->filename, $enabled) || in_array($file->name, $priority)) {
continue;
}
$priority[] = $file->name;
// Load and save pattern
if ($pattern = patterns_load_xml($file->filename)) {
patterns_save_pattern($pattern, $file->filename);
}
}
// Get profile patterns next
global $profile;
foreach (file_scan_directory('profiles/' . $profile . '/patterns', '.\\.xml') as $file) {
// Can't update existing patterns that are enabled
if (in_array($file->filename, $enabled) || in_array($file->name, $priority)) {
continue;
}
$priority[] = $file->name;
// Load and save pattern
if ($pattern = patterns_load_xml($file->filename)) {
patterns_save_pattern($pattern, $file->filename);
}
}
// Last get the default patterns
foreach (file_scan_directory(drupal_get_path('module', 'patterns') . '/patterns', '.\\.xml') as $file) {
// Can't update existing patterns that are enabled
if (in_array($file->filename, $enabled) || in_array($file->name, $priority)) {
continue;
}
$pattern = patterns_load_xml($file->filename);
// Load and save pattern
if ($pattern = patterns_load_xml($file->filename)) {
patterns_save_pattern($pattern, $file->filename);
}
}
variable_set('patterns_loaded', time());
}
$result = db_query('SELECT * FROM {patterns}');
while ($pattern = db_fetch_object($result)) {
$patterns[$pattern->pid] = $pattern;
$data = unserialize($patterns[$pattern->pid]->pattern);
$patterns[$pattern->pid]->pattern = $data;
foreach ($data as $key => $section) {
if ($data[$key]['tag'] == 'info') {
$patterns[$pattern->pid]->info = array();
array_shift($section);
foreach ($section as $property) {
$patterns[$pattern->pid]->info[$property['tag']] = $property['value'];
}
}
}
}
return $patterns;
}
function patterns_save_pattern($pattern, $xmlpath = '') {
$name = basename($xmlpath, '.xml');
foreach ($pattern[0] as $index => $value) {
if (is_numeric($index)) {
switch ($value['tag']) {
case 'title':
$title = $value['value'];
break;
case 'description':
$description = $value['value'];
break;
case 'author':
$author = $value['value'];
break;
}
}
}
if ($pid = db_result(db_query('SELECT pid FROM {patterns} WHERE name = "%s"', $name))) {
$updated = db_result(db_query('SELECT updated FROM {patterns} WHERE pid = "%d"', $pid));
if (($new_updated = filemtime(file_create_path($xmlpath))) > $updated) {
db_query('UPDATE {patterns} SET pattern = "%s", title = "%s", file = "%s", updated = "%s", description = "%s" WHERE pid = %d', serialize($pattern), $title, $xmlpath, $new_updated, $description, $pid);
}
else {
db_query('UPDATE {patterns} SET pattern = "%s", title = "%s", file = "%s", description = "%s" WHERE pid = %d', serialize($pattern), $title, $xmlpath, $description, $pid);
}
}
else {
$pid = db_next_id('{patterns}_pid');
db_query('INSERT INTO {patterns} VALUES(%d, "%s", 0, "%s", "%s", 0, "%s", "%s", "%s")', $pid, $name, $xmlpath, time(), $title, $description, serialize($pattern));
}
}
function patterns_get_pattern($id) {
if (is_numeric($id)) {
$pattern = db_fetch_object(db_query('SELECT * FROM {patterns} WHERE pid = "%d"', $id));
}
else {
if (is_string($id)) {
$pattern = db_fetch_object(db_query('SELECT * FROM {patterns} WHERE name = "%s"', $id));
}
}
// Get the actual data. Data is stored in serialized form in the db.
$pattern->pattern = unserialize($pattern->pattern);
// Rearrange the data in a nice way for each component.
// Make sure actions are processed differently so order is preserved.
$pattern->pattern = patterns_rearrange_data($pattern->pattern);
return $pattern;
}
function patterns_load_xml($path) {
if (!file_exists($path)) {
return;
}
$xml = file_get_contents($path);
$pattern = patterns_from_source($xml);
return $pattern;
}
/**
* Create a pattern from an XML data source
*/
function patterns_from_source($xml) {
$parse = xml_parser_create();
xml_parse_into_struct($parse, $xml, $vals, $index);
// Create a multi-dimensional array representing the XML structure
$pattern = current(_patterns_parse_tag($vals, 0));
return $pattern;
}
/**
* Recurse through the values of a parsed xml file to create a
* multi-dimensional representation of the data.
*/
function _patterns_parse_tag($data, $index) {
$pattern = array();
while ($current = $data[$index]) {
$type = $current['type'];
foreach ((array) $current['attributes'] as $key => $value) {
$current[strtolower($key)] = $value;
}
$current['tag'] = strtolower($current['tag']);
unset($current['type'], $current['level'], $current['attributes']);
if (!trim($current['value']) && $current['value'] != "0") {
unset($current['value']);
}
switch ($type) {
case 'open':
$index++;
$current += _patterns_parse_tag($data, &$index);
$pattern[] = $current;
break;
case 'close':
$index++;
return $pattern;
break;
case 'complete':
// In order to support more complex/non-standard features we can use serialized data
if ($current['attributes']['serialized']) {
$value = unserialize($current['value']);
if (isset($value)) {
$current['value'] = $value;
}
}
// This enables tags like <blog /> to turn into array('blog' => 'blog')
// which is useful for checkbox/select type forms
if (!isset($current['value'])) {
$current['value'] = $current['tag'];
}
$pattern[] = $current;
break;
}
$index++;
}
return $pattern;
}
function patterns_disable_pattern($pid) {
$form['pid'] = array(
'#type' => 'value',
'#value' => $pid,
);
$pattern = patterns_get_pattern($pid);
return confirm_form($form, t('Proceed with disabling pattern %pattern?', array(
'%pattern' => $pattern->title,
)), 'admin/build/patterns', '');
}
function patterns_enable_pattern($pid) {
$form['pid'] = array(
'#type' => 'value',
'#value' => $pid,
);
$disclaimer = t('Please be sure to backup your site before running a pattern. Patterns are not guaranteed to be reversable in case they do not execute well or if unforseen side effects occur.');
$pattern = patterns_get_pattern($pid);
return confirm_form($form, t('Proceed with running pattern %pattern?', array(
'%pattern' => $pattern->title,
)), 'admin/build/patterns', $disclaimer);
}
function patterns_disable_pattern_submit($form_id, $form_values) {
$pid = $form_values['pid'];
$pattern = patterns_get_pattern($pid);
if (patterns_execute_pattern($pattern, true, true)) {
return 'admin/build/patterns';
}
}
function patterns_enable_pattern_submit($form_id, $form_values) {
$pid = $form_values['pid'];
patterns_load();
$pattern = patterns_get_pattern($pid);
if (patterns_execute_pattern($pattern, false, true)) {
return 'admin/build/patterns';
}
}
function patterns_execute_pattern($pattern, $reverse = false, $interact = false, $jump = null) {
patterns_load();
set_time_limit(0);
if (!is_object($pattern)) {
$pattern = patterns_get_pattern($pattern);
if (!$pattern) {
return false;
}
}
$errors = $action_list = $identifiers = array();
$error = true;
// Pattern info
$status = $pattern->status;
$title = $pattern->title;
$pid = $pattern->pid;
// From here on out just need the actual pattern data
$pattern = $pattern->pattern;
// Split the pattern up into modules and actions. Submit modules as its
// own pattern programattically.
$modules = $actions = array();
for ($i = 0; $tag = $pattern[$i]; $i++) {
if ($tag['tag'] == 'modules') {
$modules = $tag;
}
else {
if ($tag['tag'] == 'actions') {
$actions = $tag;
unset($actions['tag']);
}
}
}
// If there are no actions or modules, most likely the pattern
// was not created correctly.
if (empty($actions) && empty($modules)) {
drupal_set_message(t('Could not recognize pattern %title, aborting.', array(
'%title' => $title,
)), 'error');
return true;
}
if ($modules && (!$interact || $interact && !$jump)) {
// Make the modules look like a normal pattern so they can be executed
// on their own.
$obj = new stdClass();
$obj->title = t('Enable/disable %title pattern modules', array(
'%title' => $title,
));
$obj->status = $status;
$obj->pattern = array(
array(
'tag' => 'actions',
$modules,
),
);
$modules = $obj;
// Modules need to be enabled first so the rest of the pattern
// can proceed smoothly.
if (!$reverse) {
$error = patterns_execute_pattern($modules, $reverse, $interact);
module_rebuild_cache();
}
}
// Keep a list of what modules handle what tags
$tag_modules = patterns_invoke($empty, 'tag modules');
// If an interactive pattern needs resuming, remove the
// already executed actions
if ($interact && $jump > 0) {
array_splice($actions, 0, $jump);
}
// Prepare actions for validation/processing
foreach ($actions as $key => $data) {
patterns_invoke($actions[$key], 'prepare');
}
// Reverse a pattern for disabling
if ($reverse && $status) {
$actions = array_reverse($actions);
foreach ($actions as $key => $data) {
$continue = patterns_invoke($data, 'reverse');
if ($continue === false) {
drupal_set_message(t('[Error] Disabling of this pattern is not supported at this time.'));
return false;
}
$actions[$key] = $data;
}
}
// Pre validate tags with their appropriate components
foreach ($actions as $key => $data) {
if (!array_key_exists($data['tag'], $tag_modules)) {
$errors[$data['tag']][] = t('Invalid Pattern: <%tag> is not a valid tag', array(
'%tag' => $data['tag'],
));
}
else {
$error = patterns_invoke($data, 'pre-validate');
if ($error) {
$errors[$data['tag']][] = t('Invalid Pattern: !msg', array(
'!msg' => $error,
));
}
}
}
if (count($errors)) {
foreach ($errors as $error) {
drupal_set_message(implode('<br>', $error), 'error');
}
return;
}
// Build and execute a list of actions
foreach ($actions as $key => $data) {
// Prepare actions for processing, ensure smooth pattern executions, and return form ids for execution
$return = patterns_invoke($data, 'form_id');
// If prepare removed the data, dont continue with this action
if (!$data || !$return) {
continue;
}
if (is_string($return)) {
$form_ids = array(
$return,
);
}
else {
if ($return) {
$form_ids = $return;
}
}
// Build the action
foreach ($form_ids as $form_id) {
$clone = $data;
$error = patterns_invoke($clone, 'validate', $form_id);
if ($error) {
if (is_array($error)) {
foreach ($error as $msg) {
drupal_set_message($msg, 'error');
}
$errors[$clone['tag']] = t('Broken Pattern: %msg', array(
'%msg' => implode('<br>', $error),
));
}
else {
drupal_set_message($error, 'error');
$errors[$clone['tag']] = t('Broken Pattern: %msg', array(
'%msg' => $error,
));
}
return;
}
// If tokens are enabled, apply tokens to the action values
// before processing
if (module_exists('token')) {
_patterns_recurse_tokens($clone, $identifiers);
//array_walk($clone, '_patterns_replace_tokens', $identifiers);
}
// Get the form data for the action
$values = patterns_invoke($clone, 'build', $form_id);
// Dont execute the action if a string was returned, indicating the pattern component
// most likely handled the action on its own and this is the message to display.
if (is_string($values)) {
drupal_set_message($values);
}
else {
// Get any extra parameters required for the action
$params = patterns_invoke($clone, 'params', $form_id, $values);
if (isset($params) && !is_array($params)) {
$params = array(
$params,
);
}
// Execute action
patterns_execute_action($form_id, $values, $params);
if (form_get_errors()) {
$descriptions = patterns_invoke($clone, 'actions');
drupal_set_message(t('An error occured running action #%num (%action)', array(
'%num' => $key + 1,
'%action' => $descriptions[$form_id],
)), 'error');
$error = true;
break;
}
}
patterns_invoke($clone, 'cleanup', $form_id);
// Clear the cache in case it causes problems
cache_clear_all();
}
if ($error) {
break;
}
// Get any primary identifiers from the action for further actions to take advantage of
$id = null;
$id = patterns_invoke($clone, 'identifier', $form_id);
if (isset($id)) {
$identifiers[$key + 1] = $id;
}
}
if (empty($errors)) {
if ($reverse) {
if ($modules) {
// Modules need to be disabled last so the rest of the pattern
// can reverse itself properly
$error = patterns_execute_pattern($modules, $reverse, $interact);
}
// Mark pattern as disabled
if ($pid) {
db_query('UPDATE {patterns} SET status = 0 WHERE pid = %d', $pid);
}
drupal_set_message(t('Pattern reversed successfully.'));
}
else {
// Mark pattern as enabled
if ($pid) {
db_query('UPDATE {patterns} SET status = 1, enabled = "%s" WHERE pid = %d', time(), $pid);
}
drupal_set_message(t('Pattern ran successfully.'));
}
}
return !$error;
}
/**
* Execute an action
*/
function patterns_execute_action($form_id, $values, $params) {
// Make sure we always have a clear cache for everything
$result = db_query('SHOW TABLES LIKE "cache_%"');
while ($table = db_fetch_array($result)) {
$table = current($table);
cache_clear_all(null, $table);
}
$args = array(
$form_id,
$values,
);
if (is_array($params)) {
$args = array_merge($args, $params);
}
patterns_executing(true);
//$form = call_user_func_array('drupal_retrieve_form', $args);
//$form['#post'] = $values;
//$return = drupal_process_form($form_id, $form);
$return = call_user_func_array('drupal_execute', $args);
patterns_executing(false);
return $return;
}
function patterns_executing($b = null) {
static $executing = false;
if (is_bool($b)) {
$executing = $b;
}
return $executing;
}
function patterns_rearrange_data($pattern) {
foreach ($pattern as $key => $value) {
if (is_string($key)) {
unset($pattern[$key]);
}
else {
if ($value['tag'] == 'actions') {
$pattern[$key] = patterns_rearrange_data($value);
$pattern[$key]['tag'] = 'actions';
}
else {
$pattern[$key] = _patterns_rearrange_data($value);
}
}
}
return $pattern;
}
/**
* Return a list of modules for a pattern
*/
function patterns_modules($pattern) {
$pattern = $pattern->pattern;
for ($i = 0; $tag = $pattern[$i]; $i++) {
if ($tag['tag'] == 'modules') {
unset($tag['tag']);
$modules = $tag;
break;
}
}
return $modules;
}
function patterns_process_modules($modules, $op = 'enable') {
// Enable at the beginning of the pattern. Disable at the end.
for ($i = 0; $module = $modules[$i]; $i++) {
if ($op == 'enable' && $module['delete'] || $op == 'disable' && !$module['delete']) {
unset($modules[$i]);
}
}
patterns_invoke($empty, 'tag modules');
$error = patterns_invoke($modules, 'pre-validate');
// Error validating modules
if ($error) {
return $error;
}
patterns_invoke($modules, 'prepare');
}
function patterns_invoke(&$data, $op, $form_id = null, $a = null) {
static $tag_modules;
if (!is_array($tag_modules) || $op == 'tag modules') {
// Get a list of tags and their modules
foreach (module_implements('patterns') as $module) {
$tags = module_invoke($module, 'patterns', 'tags');
foreach ($tags as $tag => $value) {
if (is_array($value)) {
$tag_modules[$tag] = $module;
}
else {
$tag_modules[$value] = $module;
}
}
}
}
// If you just want the modules list
if ($op == 'tag modules') {
return $tag_modules;
}
$tag = $data['tag'];
unset($data['tag']);
$module = $tag_modules[$tag];
$func = $module . '_patterns';
if (function_exists($func)) {
if ($form_id) {
$return = $func($op, $form_id, $data, $a);
}
else {
$return = $func($op, $tag, $data, $a);
}
}
$data['tag'] = $tag;
return $return;
}
function _patterns_rearrange_data($data, $parent = '') {
$numeric = array();
$count = 0;
foreach ($data as $key => $value) {
if ($value['value'] == 'false') {
$value['value'] = false;
}
else {
if ($value['value'] == 'true') {
$value['value'] = true;
}
}
if (is_numeric($key) && is_array($value) && count($value) == 2 && isset($value['tag']) && isset($value['value'])) {
unset($data[$key]);
if (isset($data[$value['tag']])) {
$numeric[] = $value['tag'];
$data[$count++] = $data[$value['tag']];
$data[$count++] = $value['value'];
unset($data[$value['tag']]);
}
else {
if (in_array($value['tag'], $numeric)) {
$data[$count++] = $value['value'];
}
else {
$data[$value['tag']] = $value['value'];
}
}
}
else {
if (is_numeric($key)) {
$tag = $value['tag'];
unset($value['tag']);
$data[$tag][] = _patterns_rearrange_data($value, $tag);
unset($data[$key]);
}
}
}
foreach ($data as $key => $value) {
if (is_array($value) && count($value) == 1 && $value[0]) {
$data[$key] = $data[$key][0];
}
}
return $data;
}
function patterns_form_alter($form_id, &$form) {
if (user_access('administer patterns') && variable_get('patterns_form_helper', true)) {
$form['#after_build'][] = 'patterns_form_helper';
}
}
function patterns_form_helper($form) {
global $form_submitted;
// static $form_id, $form_values;
$args = func_get_args();
if (!$form_id && $form_submitted) {
$form_values = $args[1];
$form_id = $form_values['form_id'];
$_SESSION['patterns_form_helper'] = array(
'form_id' => $form_id,
'form_values' => $form_values,
);
}
return $form;
}
function patterns_init() {
if (variable_get('patterns_form_helper', true) && $_SESSION['patterns_form_helper']) {
drupal_add_css(drupal_get_path('module', 'patterns') . '/patterns.css');
drupal_add_js(drupal_get_path('module', 'patterns') . '/patterns.js');
}
}
function patterns_exit($destination = null) {
if (variable_get('patterns_form_helper', true) && $_SESSION['patterns_form_helper'] && !$destination) {
print theme('patterns_form_helper', $_SESSION['patterns_form_helper']['form_id'], $_SESSION['patterns_form_helper']['form_values']);
//unset($_SESSION['patterns_form_helper']);
}
}
/**
* Implementation of hook_token_values()
*
* @If these get implementated directly into token.module, this should be removed
*/
function patterns_token_values($type, $object = NULL, $options = array()) {
if ($type == 'global') {
$path = conf_path();
$tokens['confpath'] = $path;
return $tokens;
}
}
/**
* Function callback
*/
function _patterns_modify_value(&$form) {
foreach ($form as $key => $value) {
if (is_array($value) && isset($value['#type']) && $value['#type'] == 'value') {
$form[$key]['#default_value'] = $value['#value'];
unset($form[$key]['#value']);
}
else {
if (is_array($value)) {
_patterns_modify_value($form[$key]);
}
}
}
}
/**
* Recurse an array and replace with tokens
* @ This is used instead of array_walk_recursive because
* of some strange issues with token_get_values failing.
*/
function _patterns_recurse_tokens(&$object, $identifiers) {
foreach ($object as $key => $value) {
if (is_array($value)) {
_patterns_recurse_tokens($object[$key], $identifiers);
}
else {
if (is_scalar($value)) {
$old_key = $key;
_patterns_replace_tokens($object[$key], $key, $identifiers);
// The key was changed, change it
if ($old_key != $key) {
$keys = array_keys($object);
$keys[array_search($old_key, $keys)] = $key;
$object = array_combine($keys, array_values($object));
}
}
}
}
}
/**
* Array walk callback to replace tokens inside form values
*/
function _patterns_replace_tokens(&$a, &$b, $identifiers = array()) {
static $count = 0;
// Replace IDs with identifiers from the current executing pattern
if (preg_match('/@([0-9]+)@/', $a, $match)) {
$a = str_replace($match[0], $identifiers[$match[1]], $a);
}
if (preg_match('/__([0-9]+)__/', $b, $match)) {
$b = str_replace($match[0], $identifiers[$match[1]], $a);
}
// Replace tokens
$a = token_replace($a, 'global', NULL, '@', '@');
$b = token_replace($b, 'global', NULL, '__', '__');
}
/**
* Check if a .htaccess file exists to prevent downloads of pattern files
*/
function _patterns_check_file_dir() {
return false;
$path = file_create_path(variable_get('patterns_save_xml', 'patterns'));
if (!is_file($path . '/.htaccess')) {
$content = '# Prevent downloading site patterns
<FilesMatch "\\.xml$">
Order allow,deny
</FilesMatch>
';
file_save_data($content, $path . '/.htaccess');
}
}
function theme_patterns_form_helper_menu($forms) {
$output = '<ul class="patterns-form-menu">';
foreach ($forms as $form_id => $values) {
$output .= '<li class="patterns-form-menu-item">' . $form_id . '</li>';
}
$output .= '</li>';
return $output;
}
function theme_patterns_form_helper($form_id, $values) {
$output = '<div class="patterns-form" id="patterns-form-' . $form_id . '">';
$output .= '<div class="patterns-form-title">' . t('Form values for %key', array(
'%key' => $form_id,
)) . '</div>';
foreach ($values as $key => $value) {
$output .= '<div class="patterns-form-item"><div class="patterns-form-key">' . $key . ' => </div>';
$output .= '<div class="patterns-form-value">' . print_r($value, true) . '</div></div>';
}
$output .= '</div>';
return $output;
}
Functions
Name | Description |
---|---|
patterns_disable_pattern | |
patterns_disable_pattern_submit | |
patterns_edit | Menu callback to edit a patterns data |
patterns_edit_submit | Submit edits to the pattern |
patterns_edit_validate | Validate pattern modifications (make sure proper XML) |
patterns_enable_pattern | |
patterns_enable_pattern_submit | |
patterns_execute_action | Execute an action |
patterns_execute_pattern | |
patterns_executing | |
patterns_exit | |
patterns_form_alter | |
patterns_form_helper | |
patterns_from_source | Create a pattern from an XML data source |
patterns_get_pattern | |
patterns_get_patterns | |
patterns_import_file | Display the import pattern file form |
patterns_import_source | Display the import pattern form |
patterns_import_submit | |
patterns_import_url | Display the import pattern url form |
patterns_import_validate | |
patterns_init | |
patterns_invoke | |
patterns_list | |
patterns_load | |
patterns_load_xml | |
patterns_menu | Implementation of hook_menu(). |
patterns_modules | Return a list of modules for a pattern |
patterns_modules_page | List the modules used by a particular pattern |
patterns_perm | Implementation of hook_perm(). |
patterns_process_modules | |
patterns_rearrange_data | |
patterns_revert | Menu callback to undo a patterns update changes |
patterns_save_pattern | |
patterns_token_values | Implementation of hook_token_values() |
theme_patterns_form_helper | |
theme_patterns_form_helper_menu | |
_patterns_check_file_dir | Check if a .htaccess file exists to prevent downloads of pattern files |
_patterns_modify_value | Function callback |
_patterns_parse_tag | Recurse through the values of a parsed xml file to create a multi-dimensional representation of the data. |
_patterns_rearrange_data | |
_patterns_recurse_tokens | Recurse an array and replace with tokens @ This is used instead of array_walk_recursive because of some strange issues with token_get_values failing. |
_patterns_replace_tokens | Array walk callback to replace tokens inside form values |