From 8fbb84142d80a04c8b9ba8ccf671e40a19245cd5 Mon Sep 17 00:00:00 2001
From: "Brandon C. Kesselly" ". $section['body'] ." ". $section['more'] ." Or you can restore one of the files in your saved backup destinations. ' . t('Choose the type of destination you would like to create:') . ' NodeSquirrel is the cloud backup service built by the maintainers of Backup and Migrate.". $section['title'] ."
";
+ }
+ $out .= "' . $vars['title'] . '
';
+ }
+ if (!empty($vars['description'])) {
+ $output .= ''. implode('', $group['items']) .'
';
+ $output .= theme('backup_migrate_group', $group);
+ }
+ }
+ }
+ else {
+ $output = t('No destination types available.');
+ }
+ $form['select_type'] = array(
+ '#type' => 'markup',
+ '#markup' => $output,
+ );
+ }
+ return $form;
+ }
+
+ /**
+ * Get the message to send to the user when confirming the deletion of the item.
+ */
+ function delete_confirm_message() {
+ return t('Are you sure you want to delete the destination %name? Backup files already saved to this destination will not be deleted.', array('%name' => $this->get_name()));
+ }
+
+ /**
+ * Get a boolean representing if the destination is remote or local.
+ */
+ function get_remote() {
+ return $this->op('remote backup');
+ }
+
+ /**
+ * Get the action links for a destination.
+ */
+ function get_action_links() {
+ $out = parent::get_action_links();
+ $item_id = $this->get_id();
+
+ // Don't display the download/delete/restore ops if they are not available for this destination.
+ if ($this->op('list files') && user_access("access backup files")) {
+ $out = array('list files' => l(t("list files"), $this->get_settings_path() . '/list/files/'. $item_id)) + $out;
+ }
+ if (!$this->op('configure') || !user_access('administer backup and migrate')) {
+ unset($out['edit']);
+ }
+ return $out;
+ }
+
+ /**
+ * Get the action links for a file on a given destination.
+ */
+ function get_file_links($file_id) {
+ $out = array();
+
+ // Don't display the download/delete/restore ops if they are not available for this destination.
+ $can_read = $this->can_read_file($file_id);
+ $can_delete = $this->can_delete_file($file_id);
+
+ $path = $this->get_settings_path();
+
+ $destination_id = $this->get_id();
+ if ($can_read && user_access("access backup files")) {
+ $out[] = l(t("download"), $path . '/downloadfile/'. $destination_id .'/'. $file_id);
+ }
+ if ($can_read && user_access("restore from backup")) {
+ $out[] = l(t("restore"), $path . '/list/restorefile/' . $destination_id .'/'. $file_id);
+ }
+ if ($can_delete && user_access("delete backup files")) {
+ $out[] = l(t("delete"), $path . '/list/deletefile/' . $destination_id .'/'. $file_id);
+ }
+ return $out;
+ }
+
+ /**
+ * Determine if we can read the given file.
+ */
+ function can_read_file($file_id) {
+ return $this->op('restore');
+ }
+
+ /**
+ * Determine if we can read the given file.
+ */
+ function can_delete_file($file_id) {
+ return $this->op('delete');
+ }
+
+ /**
+ * Get the form for the settings for this destination type.
+ */
+ function settings_default() {
+ return array();
+ }
+
+ /**
+ * Get the form for the settings for this destination.
+ */
+ function settings_form($form) {
+ return $form;
+ }
+
+ /**
+ * Validate the form for the settings for this destination.
+ */
+ function settings_form_validate($form_values) {
+ }
+
+ /**
+ * Submit the settings form. Any values returned will be saved.
+ */
+ function settings_form_submit($form_values) {
+ return $form_values;
+ }
+
+ /**
+ * Check that a destination is valid.
+ */
+ function confirm_destination() {
+ return true;
+ }
+
+ /**
+ * Add the menu items specific to the destination type.
+ */
+ function get_menu_items() {
+ $items = parent::get_menu_items();
+
+ $path = $this->get_settings_path();
+
+ $items[$path . '/list/files'] = array(
+ 'title' => 'Destination Files',
+ 'page callback' => 'backup_migrate_menu_callback',
+ 'page arguments' => array('destinations', 'backup_migrate_ui_destination_display_files', TRUE),
+ 'access arguments' => array('access backup files'),
+ 'type' => MENU_LOCAL_TASK,
+ );
+ $items[$path . '/list/deletefile'] = array(
+ 'title' => 'Delete File',
+ 'description' => 'Delete a backup file',
+ 'page callback' => 'backup_migrate_menu_callback',
+ 'page arguments' => array('destinations', 'backup_migrate_ui_destination_delete_file', TRUE),
+ 'access arguments' => array('delete backup files'),
+ 'type' => MENU_LOCAL_TASK,
+ );
+ $items[$path . '/list/restorefile'] = array(
+ 'title' => 'Restore from backup',
+ 'description' => 'Restore database from a backup file on the server',
+ 'page callback' => 'backup_migrate_menu_callback',
+ 'page arguments' => array('destinations', 'backup_migrate_ui_destination_restore_file', TRUE),
+ 'access arguments' => array('restore from backup'),
+ 'type' => MENU_LOCAL_TASK,
+ );
+ $items[$path . '/downloadfile'] = array(
+ 'title' => 'Download File',
+ 'description' => 'Download a backup file',
+ 'page callback' => 'backup_migrate_menu_callback',
+ 'page arguments' => array('destinations', 'backup_migrate_ui_destination_download_file', TRUE),
+ 'access arguments' => array('access backup files'),
+ 'type' => MENU_CALLBACK,
+ );
+ return $items;
+ }
+
+
+}
+
+/**
+ * A base class for creating destinations.
+ */
+class backup_migrate_destination_remote extends backup_migrate_destination {
+ /**
+ * The location is a URI so parse it and store the parts.
+ */
+ function get_location() {
+ return $this->url(FALSE);
+ }
+
+ /**
+ * The location to display is the url without the password.
+ */
+ function get_display_location() {
+ return $this->url(TRUE);
+ }
+
+ /**
+ * Return the location with the password.
+ */
+ function set_location($location) {
+ $this->location = $location;
+ $this->set_url($location);
+ }
+
+ /**
+ * Destination configuration callback.
+ */
+ function edit_form() {
+ $form = parent::edit_form();
+ $form['scheme'] = array(
+ "#type" => "textfield",
+ "#title" => t("Scheme"),
+ "#default_value" => @$this->dest_url['scheme'] ? $this->dest_url['scheme'] : '',
+ "#required" => TRUE,
+ "#weight" => 0,
+ );
+ $form['host'] = array(
+ "#type" => "textfield",
+ "#title" => t("Host"),
+ "#default_value" => @$this->dest_url['host'] ? $this->dest_url['host'] : 'localhost',
+ "#required" => TRUE,
+ "#weight" => 10,
+ );
+ $form['path'] = array(
+ "#type" => "textfield",
+ "#title" => t("Path"),
+ "#default_value" => @$this->dest_url['path'],
+ "#required" => TRUE,
+ "#weight" => 20,
+ );
+ $form['user'] = array(
+ "#type" => "textfield",
+ "#title" => t("Username"),
+ "#default_value" => @$this->dest_url['user'],
+ "#required" => TRUE,
+ "#weight" => 30,
+ );
+ $form['pass'] = array(
+ "#type" => "password",
+ "#title" => t("Password"),
+ "#default_value" => @$this->dest_url['pass'],
+ '#description' => '',
+ "#weight" => 40,
+ );
+ if (@$this->dest_url['pass']) {
+ $form['old_password'] = array(
+ "#type" => "value",
+ "#value" => @$this->dest_url['pass'],
+ );
+ $form['pass']["#description"] .= t(' You do not need to enter a password unless you wish to change the currently saved password.');
+ }
+ return $form;
+ }
+
+ /**
+ * Submit the configuration form. Glue the url together and add the old password back if a new one was not specified.
+ */
+ function edit_form_submit($form, &$form_state) {
+ $form_state['values']['pass'] = $form_state['values']['pass'] ? $form_state['values']['pass'] : $form_state['values']['old_password'];
+ $form_state['values']['location'] = $this->glue_url($form_state['values'], FALSE);
+ parent::edit_form_submit($form, $form_state);
+ }
+}
+
diff --git a/sites/all/modules/contrib/backup_migrate/includes/destinations.nodesquirrel.inc b/sites/all/modules/contrib/backup_migrate/includes/destinations.nodesquirrel.inc
new file mode 100644
index 0000000..a929f07
--- /dev/null
+++ b/sites/all/modules/contrib/backup_migrate/includes/destinations.nodesquirrel.inc
@@ -0,0 +1,980 @@
+confirm_destination()) {
+ return $destination;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Get the NS destination for the given key.
+ */
+function backup_migrate_nodesquirrel_get_destination($secret_key) {
+ if ($secret_key) {
+ backup_migrate_include('destinations');
+ $destination = backup_migrate_create_destination('nodesquirrel', array('machine_name' => 'nodesquirrel'));
+ $destination->settings['secret_key'] = $secret_key;
+ return $destination;
+ }
+ return NULL;
+}
+
+
+/**
+ * Get a helper link to activate a site and create a tree.
+ */
+function backup_migrate_nodesquirrel_get_activate_help_text() {
+ $activate_link = backup_migrate_nodesquirrel_get_activate_link();
+ return array(
+ '#type' => 'item',
+ '#title' => t('Need a Secret Key?'),
+ '#markup' => t('Visit !nodesquirrel.', array('!nodesquirrel' => $activate_link)),
+ '#description' => t('Don\'t worry if you don\'t have an account yet. You can create one when you get there.'),
+ );
+}
+
+/**
+ * Get a helper link to activate a site and create a tree.
+ */
+function backup_migrate_nodesquirrel_get_activate_link() {
+ $activate_link = l('nodesquirrel.com/activate', variable_get('nodesquirrel_activate_url', 'http://manage.nodesquirrel.com/activate'), array('query' => array('url' => url('', array('absolute' => TRUE)), 'email' => variable_get('site_mail', ''), 'configure' => url($_GET['q'], array('absolute' => TRUE)))));
+ return $activate_link;
+}
+
+/**
+ * Get a helper link to activate a site and create a tree.
+ */
+function backup_migrate_nodesquirrel_get_manage_link($destination) {
+ $url = variable_get('nodesquirrel_manage_url', 'https://manage.nodesquirrel.com') . '/backups/' . $destination->_get_destination();
+ return l($url, $url);
+}
+
+/**
+ * Get a helper link to activate a site and create a tree.
+ */
+function backup_migrate_nodesquirrel_get_plan_link() {
+ $url = variable_get('nodesquirrel_manage_url', 'https://manage.nodesquirrel.com') . '/plan';
+ return l($url, $url);
+}
+
+/**
+ * Get the path to the nodesquirrel settings tab.
+ */
+function backup_migrate_nodesquirrel_settings_path() {
+ $path = BACKUP_MIGRATE_MENU_PATH . '/settings/destinations/nodesquirrel/edit';
+ return $path;
+}
+
+
+/**
+ * NodeSquirrel settings page callback.
+ */
+function backup_migrate_nodesquirrel_settings() {
+ return drupal_get_form('backup_migrate_nodesquirrel_settings_form');
+}
+
+/**
+ * NodeSquirrel settings form.
+ */
+function backup_migrate_nodesquirrel_credentials_settings_form($key = '', $status) {
+ $collapse = !empty($status);
+ $form['nodesquirrel_credentials'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('NodeSquirrel Credentials'),
+ '#collapsible' => $collapse,
+ '#collapsed' => $collapse,
+ );
+
+ $form['nodesquirrel_credentials']['nodesquirrel_secret_key'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Secret Key'),
+ '#size' => 80,
+ '#default_value' => $key,
+ );
+ if (empty($key)) {
+ $form['nodesquirrel_credentials']['secret_key_help'] = backup_migrate_nodesquirrel_get_activate_help_text();
+ }
+
+ return $form;
+}
+
+/**
+ * Return a form element with some help text describing NodeSquirrel.
+ */
+function backup_migrate_nodesquirrel_info_form() {
+ $form = array();
+
+ $form['nodesquirrel_info'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('What is NodeSquirrel?'),
+ );
+
+ $form['nodesquirrel_info']['intro'] = array(
+ '#type' => 'markup',
+ '#markup' => t('
Create your free account at !signup
', array('!nodesquirrel' => l('nodesquirrel.com', 'http://www.nodesquirrel.com'), '!signup' => backup_migrate_nodesquirrel_get_activate_link())), + ); + return $form; +} + +/** + * Display the NodeSquirrel status on the configuration form. + */ +function backup_migrate_nodesquirrel_status_form($key, $destination, $status) { + $form = array(); + + $form['nodesquirrel_status'] = array( + '#type' => 'fieldset', + '#title' => t('NodeSquirrel Status'), + ); + $form['nodesquirrel_status']['status'] = array( + '#type' => 'item', + '#title' => t('NodeSquirrel Status'), + '#markup' => t('Not Configured. Enter your Secret Key below to get started.'), + ); + + // Warn the user if the key they entered is invalid. + if ($key && empty($status)) { + $form['nodesquirrel_status']['status']['#markup'] = t('Your secret key does not seem to be valid. Please check that you entered it correctly or visit !ns to generate a new key.', array('!ns' => backup_migrate_nodesquirrel_get_activate_link())); + } + else if (!empty($destination) && is_array($status)) { + if (!empty($status['lifetime_backups_used']) && !empty($status['lifetime_backups']) && $status['lifetime_backups_used'] >= $status['lifetime_backups']) { + $form['nodesquirrel_status']['status']['#markup'] = t('Your !num backup trial has expired. Visit !link to continue backing up.', array('!num' => $status['lifetime_backups'], '!link' => backup_migrate_nodesquirrel_get_plan_link())); + } + else { + $form['nodesquirrel_status']['status']['#markup'] = t('Ready to Backup'); + if (user_access('perform backup')) { + $form['nodesquirrel_status']['status']['#markup'] .= ' ' . l('(' . t('backup now') . ')', BACKUP_MIGRATE_MENU_PATH); + } + } + if (!empty($status['plan_name'])) { + $form['nodesquirrel_status']['plan_name'] = array( + '#type' => 'item', + '#title' => t('Current Plan'), + '#markup' => check_plain($status['plan_name']) + ); + + if (isset($status['plan_id']) && strpos($status['plan_id'], 'trial') !== FALSE) { + if (isset($status['lifetime_backups']) && isset($status['lifetime_backups_used'])) { + $remains = $status['lifetime_backups'] - $status['lifetime_backups_used']; + $remains = $remains > 0 ? $remains : t('none'); + $form['nodesquirrel_status']['plan_name']['#markup'] .= ' ' . t('(@remains remaining of @backups backup trial)', array('@backups' => $status['lifetime_backups'], '@remains' => $remains)); + } + + if (isset($status['lifespan']) && isset($status['age']) && $status['lifespan'] > 0) { + $remains = ceil(($status['lifespan'] - $status['age']) / 86400); + if ($remains <= 0) { + $form['nodesquirrel_status']['plan_name']['#markup'] .= ' ' . t('(Your !span day trial has expired.)', array('!span' => ceil($status['lifespan'] / 86400))); + } + else { + $form['nodesquirrel_status']['plan_name']['#markup'] .= ' ' . format_plural($remains, '(1 day remaining)', '(!span days remaining)', array('!span' => ceil($remains))); + } + } + } + + } + + if (isset($status['backups_used'])) { + $form['nodesquirrel_status']['backups_used'] = array( + '#type' => 'item', + '#title' => t('Number of Stored Backups'), + '#markup' => $status['backups_used'] == 0 ? t('None') : number_format($status['backups_used']) + ); + } + + if (isset($status['last_backup'])) { + $form['nodesquirrel_status']['last_backup'] = array( + '#type' => 'item', + '#title' => t('Last Backup'), + '#markup' => empty($status['last_backup']) ? t('Never') : t('!date (!ago ago)', array('!date' => format_date($status['last_backup'], 'small'), '!ago' => format_interval(time() - $status['last_backup'], 1))) + ); + } + if ($status['bytes_per_locker']) { + if (isset($status['bytes_used'])) { + $form['nodesquirrel_status']['space'] = array( + '#type' => 'item', + '#title' => t('Storage Space'), + '#markup' => t('!used used of !total (!remaining remaining)', array('!used' => backup_migrate_format_size($status['bytes_used']), '!total' => backup_migrate_format_size($status['bytes_per_locker']), '!remaining' => backup_migrate_format_size(max(0, $status['bytes_per_locker'] - $status['bytes_used'])))) + ); + } + else { + $form['nodesquirrel_status']['space'] = array( + '#type' => 'item', + '#title' => t('Total Storage Space'), + '#markup' => t('!total', array('!total' => backup_migrate_format_size($status['bytes_per_locker']))) + ); + } + } + $form['nodesquirrel_status']['manage'] = array( + '#type' => 'item', + '#title' => t('Management Console'), + '#markup' => backup_migrate_nodesquirrel_get_manage_link($destination), + '#description' => t('You can use the NodeSquirrel management console to add and edit your sites, reset your secret key, download and delete backups, and modify your NodeSquirrel account.'), + ); + + } + + return $form; +} + +function backup_migrate_nodesquirrel_schedule_settings_form($destination, $status) { + backup_migrate_include('sources', 'schedules', 'profiles'); + + // If the schedule has been overriden it must be edited in the schedule tab. + $schedule = backup_migrate_crud_get_item('schedule', 'nodesquirrel'); + + $default = 60*60*24; + + $form = array(); + $form['nodesquirrel_schedule'] = array( + '#type' => 'fieldset', + '#title' => t('Backup Schedule'), + '#description' => t('Set up a schedule to back up your site to NodeSquirrel. You can customize this schedule or add additional schedules in the !schedule.', array('!schedule' => l(t('Schedules tab'), BACKUP_MIGRATE_MENU_PATH . '/schedule'), '!cron' => l(t('cron'), 'http://drupal.org/cron'))), + ); + + $key = 'nodesquirrel_schedule'; + $form['nodesquirrel_schedule'][$key] = array(); + $defaults = array( + 'period' => empty($schedule) ? variable_get('nodesquirrel_schedule', 60*60*24) : $schedule->get('period'), + 'enabled' => empty($schedule) ? variable_get('nodesquirrel_schedule_enabled', TRUE) : $schedule->get('enabled'), + 'source_id' => empty($schedule) ? variable_get('nodesquirrel_schedule_source_id', 'db') : $schedule->get('source_id'), + 'keep' => empty($schedule) ? variable_get('nodesquirrel_schedule_keep', BACKUP_MIGRATE_SMART_DELETE) : $schedule->get('keep'), + ); + + $form['nodesquirrel_schedule'][$key]['nodesquirrel_schedule_enabled'] = array( + '#type' => 'checkbox', + '#title' => t('Automatically backup to NodeSquirrel'), + '#default_value' => $defaults['enabled'], + ); + $form['nodesquirrel_schedule'][$key]['settings'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'nodesquirrel_schedule_enabled' => TRUE, + ), + ); + $form['nodesquirrel_schedule'][$key]['settings']['nodesquirrel_schedule_source_id'] = _backup_migrate_get_source_pulldown($defaults['source_id']); + $options = array( + (60*60) => t('Once an hour'), + (60*60*24) => t('Once a day'), + (60*60*24*7) => t('Once a week'), + ); + $period = $defaults['period']; + if (!isset($options[$period])) { + $options[$period] = empty($schedule) ? t('Custom') : $schedule->get('frequency_description'); + } + $form['nodesquirrel_schedule'][$key]['settings']['nodesquirrel_schedule'] = array( + '#type' => 'select', + '#title' => t('Schedule Frequency'), + '#options' => $options, + '#default_value' => $period, + ); + $form['nodesquirrel_schedule'][$key]['settings']['nodesquirrel_schedule_keep'] = array( + '#type' => 'checkbox', + '#title' => t('Automatically delete old backups'), + '#return_value' => $defaults['keep'] == BACKUP_MIGRATE_KEEP_ALL ? BACKUP_MIGRATE_SMART_DELETE : $defaults['keep'], + '#default_value' => $defaults['keep'], + ); + return $form; +} + +/** + * NodeSquirrel settings form. + */ +function backup_migrate_nodesquirrel_settings_form($form_state) { + _backup_migrate_message_callback('_backup_migrate_message_browser'); + + $form = array(); + + $key = variable_get('nodesquirrel_secret_key', ''); + $status = array(); + if ($destination = backup_migrate_nodesquirrel_get_destination($key)) { + $status = $destination->check_limits(); + } + + $form += backup_migrate_nodesquirrel_info_form(); + $form += backup_migrate_nodesquirrel_status_form($key, $destination, $status); + $form += backup_migrate_nodesquirrel_credentials_settings_form($key, $status); + $form += backup_migrate_nodesquirrel_schedule_settings_form($destination, $status); + + return system_settings_form($form); +} + + +/** + * A destination for sending database backups to the NodeSquirel backup service. + * + * @ingroup backup_migrate_destinations + */ +class backup_migrate_destination_nodesquirrel extends backup_migrate_destination { + var $supported_ops = array('scheduled backup', 'manual backup', 'remote backup', 'restore', 'list files', 'configure', 'delete'); + var $cache_files = TRUE; + // Don't generate a metadata file as NodeSquirrel can save metadata natively. + var $save_metadata = FALSE; + + /** + * Get the destination name. Provide a default. + */ + function get_name() { + if (empty($this->name)) { + return t('NodeSquirrel'); + } + return $this->name; + } + + /** + * Get the menu items for manipulating this type. + */ + function get_menu_items() { + $items = array(); + $path = BACKUP_MIGRATE_MENU_PATH . '/nodesquirrel'; + $items[$path] = array( + 'title' => t('NodeSquirrel'), + 'page callback' => 'backup_migrate_menu_callback', + 'page arguments' => array('destinations.nodesquirrel', 'backup_migrate_nodesquirrel_settings'), + 'access arguments' => array('administer backup and migrate'), + 'weight' => 10, + 'type' => MENU_LOCAL_TASK, + ); + return $items; + } + + /** + * Declare any mysql databases defined in the settings.php file as a possible destination. + */ + function destinations() { + $out = array(); + $out['nodesquirrel'] = backup_migrate_create_destination('nodesquirrel', array('machine_name' => 'nodesquirrel')); + if ($secret_key = variable_get('nodesquirrel_secret_key', '')) { + $out['nodesquirrel']->settings['secret_key'] = $secret_key; + } + else { + $out['nodesquirrel']->supported_ops = array('manual backup', 'remote backup'); + } + return $out; + } + + /** + * Returns a form to be completed before saving to the destination can be complete. + */ + function before_backup_form($settings) { + $form = array(); + $key = $this->settings('secret_key'); + if (empty($key)) { + $form += backup_migrate_nodesquirrel_info_form(); + $form += backup_migrate_nodesquirrel_credentials_settings_form(); + } + return $form; + } + + /** + * Validate the before_backup_form form. + */ + function before_backup_form_validate($settings, $form, $form_state) { + if (isset($form_state['values']['nodesquirrel_secret_key'])) { + $key = trim($form_state['values']['nodesquirrel_secret_key']); + if ($error = $this->vaidate_key($key)) { + form_set_error('secret_key', $error); + } + } + } + + /** + * Submit the before_backup_form form. + */ + function before_backup_form_submit($settings, $form, $form_state) { + if (isset($form_state['values']['nodesquirrel_secret_key'])) { + $this->settings['secret_key'] = $form_state['values']['nodesquirrel_secret_key']; + variable_set('nodesquirrel_secret_key', $this->settings['secret_key']); + } + } + + + /** + * Save to the NodeSquirrel destination. + */ + function save_file($file, $settings) { + if ($destination = $this->_get_destination()) { + srand((double)microtime()*1000000); + + $filename = $file->filename(); + $filesize = filesize($file->filepath()); + $ticket = $this->_xmlrpc('backups.getUploadTicket', array($destination, $filename, $filesize, $file->file_info)); + + if ($ticket) { + + $url = $ticket['url']; + + // If the ticket requires authentication add our username/password to the url. + if (!empty($ticket['auth']) && $ticket['auth'] = 'basic') { + $parts = parse_url($ticket['url']); + list($parts['user'], $parts['pass']) = $this->get_user_pass(); + $url = $this->glue_url($parts, FALSE); + } + + $out = $this->_post_file($url, 'POST', $ticket['params'], $file); + + if ($out->code == 200) { + // Confirm the upload. + $confirm = $this->_xmlrpc('backups.confirmUpload', array($destination, $filename, $filesize)); + + if ($confirm['success']) { + // Set a message with a link to the manage console. + $url = variable_get('nodesquirrel_manage_url', 'http://manage.nodesquirrel.com') . '/backups/' . $this->_get_destination(); + _backup_migrate_message('Your backup has been saved to your NodeSquirrel account. View it at !account', array('!account' => l($url, $url))); + + return $file; + } + else { + _backup_migrate_message('The backup file never made it to the NodeSquirrel backup server. There may have been a network problem. Please try again later'); + } + } + else { + $error = !empty($out->headers['x-bams-error']) ? $out->headers['x-bams-error'] : $out->error; + _backup_migrate_message('The NodeSquirrel server returned the following error: %err', array('%err' => $error), 'error'); + } + } + else if ($err = xmlrpc_error()) { + // XMLRPC errors are already handled by the server function below. + } + else { + _backup_migrate_message('The NodeSquirrel server refused the backup but did not specify why. Maybe the server is down.'); + } + } + return NULL; + } + + /** + * Load from the NodeSquirrel destination. + */ + function load_file($file_id) { + if ($destination = $this->_get_destination()) { + backup_migrate_include('files'); + $file = new backup_file(array('filename' => $file_id)); + + $ticket = $this->_xmlrpc('backups.getDownloadTicket', array($destination, $file_id)); + + if ($ticket && $url = $ticket['url']) { + // If the ticket requires authentication add our username/password to the url. + if (!empty($ticket['auth']) && $ticket['auth'] = 'basic') { + $parts = parse_url($ticket['url']); + $parts['user'] = @$this->dest_url['user']; + $parts['pass'] = @$this->dest_url['pass']; + $url = $this->glue_url($parts, FALSE); + } + + $out = drupal_http_request($url); + + if ($out->code == 200) { + file_put_contents($file->filepath(), $out->data); + return $file; + } + else { + $error = !empty($out->headers['x-bams-error']) ? $out->headers['x-bams-error'] : $out->error; + _backup_migrate_message('The server returned the following error: %err', array('%err' => $error), 'error'); + } + } + } + return NULL; + } + + /** + * Delete from the NodeSquirrel destination. + */ + function delete_file($file_id) { + if ($destination = $this->_get_destination()) { + $result = $this->_xmlrpc('backups.deleteFile', array($destination, $file_id)); + } + } + + /** + * List the files in the remote destination. + */ + function _list_files() { + $files = array(); + backup_migrate_include('files'); + + if ($destination = $this->_get_destination()) { + $file_list = $this->_xmlrpc('backups.listFiles', array($destination)); + foreach ((array)$file_list as $file) { + $files[$file['filename']] = new backup_file($file); + } + } + + return $files; + } + + /** + * List the files in the remote destination. + */ + function check_limits() { + if (empty($this->limits)) { + $this->limits = $this->_xmlrpc('backups.getLimits', array($this->_get_destination())); + } + return $this->limits; + } + + /** + * Check that a destination is valid. + */ + function confirm_destination() { + return $this->check_limits(); + } + + /** + * Get the form for the settings for this destination. + */ + function edit_form() { + $form = parent::edit_form(); + + // If this is a new destination but the default NS destination has not been created yet, + // redirect to the NS config screen. + if (!$this->get_id() && !variable_get('nodesquirrel_secret_key', '')) { + drupal_goto(BACKUP_MIGRATE_MENU_PATH . '/nodesquirrel'); + } + + $form['settings'] = array('#tree' => TRUE); + $activate_link = backup_migrate_nodesquirrel_get_activate_link(); + + // Retrieve the key from the settings or get it from the get string if this is an auto-config action. + $key = $this->settings('secret_key'); + if (!empty($_GET['key']) && preg_match(NODESQUIRREL_SECRET_KEY_PATTERN, $_GET['key'])) { + $key = $_GET['key']; + } + + $form['settings']['secret_key'] = array( + '#type' => 'textfield', + '#title' => t('Secret Key'), + '#default_value' => $key, + ); + $form['settings']['location'] = array('#type' => 'value', '#value' => ''); + + $form['settings']['secret_key_help'] = array( + '#type' => 'item', + '#title' => t('Need a Secret Key?'), + '#markup' => t('Visit !nodesquirrel.', array('!nodesquirrel' => $activate_link)), + ); + + return $form; + } + + /** + * Submit the configuration form. Glue the url together and add the old password back if a new one was not specified. + */ + function edit_form_validate($form, &$form_state) { + $key = trim($form_state['values']['settings']['secret_key']); + if ($error = $this->vaidate_key($key)) { + form_set_error('secret_key', $error); + } + } + + /** + * Validate a secret key. Returns error text if the key is invalid. + */ + function vaidate_key($key) { + $error = FALSE; + if ($key) { + if (!preg_match(NODESQUIRREL_SECRET_KEY_PATTERN, $key)) { + return 'The secret key you entered is not the right format. Please make sure you copy it exactly.'; + } + $this->settings['secret_key'] = check_plain($key); + + $limits = $this->check_limits(); + + if (!$limits) { + $err = xmlrpc_error(); + if (!empty($err->code) && $err->code == '401') { + return 'Could not log in to the NodeSquirrel server. Please check that your secret key is correct.'; + } + else { + return 'Your site could not be found on NodeSquirrel. Please check that your secret key is correct.'; + } + } + } + else { + return 'You must enter a NodeSquirrel secret key.'; + } + return FALSE; + } + + /** + * Submit the configuration form. Glue the url together and add the old password back if a new one was not specified. + */ + function edit_form_submit($form, &$form_state) { + $form_state['values']['secret_key'] = check_plain($form_state['values']['settings']['secret_key']); + parent::edit_form_submit($form, $form_state); + } + + /** + * Get the destination id or warn the user that it has not been set. + */ + function _get_destination($warn = TRUE) { + list($id, $key) = $this->get_user_pass(); + return $id; + } + + /** + * Get the destination id or warn the user that it has not been set. + */ + function _get_private_key($warn = TRUE) { + list($id, $key) = $this->get_user_pass(); + return $key; + } + + /** + * Break the secret key into the public/private key (user/pass). + */ + function get_user_pass() { + $key = $this->settings('secret_key'); + // The username is the first 32 chars. + $user = substr($key, 0, 32); + // The pass is the last 32 chars. There may be a separating character. + $pass = substr($key, strlen($key) - 32); + return array($user, $pass); + } + + function get_display_location() { + return t('NodeSquirrel.com'); + } + + function add_scheme($url) { + return 'http://' . $url; + } + + /** + * Get the form for the settings for this destination. + */ + function _xmlrpc($method, $args = array()) { + // Retrieve the severs or read them from a stored variable. + $servers = $this->_get_endpoints(); + + // Do the actual call. + return $this->__xmlrpc($method, $args, $servers); + } + + /** + * Get the form for the settings for this destination. + */ + function __xmlrpc($method, $args, $servers, $retry = 3) { + if ($servers && --$retry > 0) { + // Add the key authentication arguments if we can. + if ($this->_sign_request($args)) { + $url = reset($servers); + // Try each available server in order. + while ($url) { + + $url = $this->add_scheme($url); + + $out = xmlrpc($url, array($method => $args)); + + // Check for errors. + $err = xmlrpc_error(); + if ($err && $err->is_error) { + switch ($err->code) { + case '500': + case '503': + case '404': + // Some sort of server error. Try the next one. + $url = next($servers); + + // If we're at the end of the line then try refetching the urls + if (!$url) { + $servers = $this->_get_endpoints(TRUE, $retry); + return $this->__xmlrpc($method, $args, $servers, $retry); + } + break; + case '300': + // 'Multiple Choices' means that the existing server list needs to be refreshed. + $servers = $this->_get_endpoints(TRUE, $retry); + return $this->__xmlrpc($method, $args, $servers, $retry); + break; + case '401': + case '403': + // Authentication failed. + _backup_migrate_message('Couldn\'t log in to NodeSquirrel. The server error was: %err', array('%err' => $err->message), 'error'); + return FALSE; + break; + default: + // Some sort of client error. Don't try the next server because it'll probably say the same thing. + _backup_migrate_message('The NodeSquirrel server returned the following error: %err', array('%err' => $err->message), 'error'); + return FALSE; + break; + } + } + // No error, return the result. + else { + return $out; + } + } + } + } + } + + /** + * Genrate a hash with a given secret key, timestamp and random value. + */ + function _get_hash($time, $nonce) { + if ($private_key = $this->_get_private_key()) { + $message = $time . ':' . $nonce . ':' . $private_key; + // Use HMAC-SHA1 to authenticate the call. + $hash = base64_encode( + pack('H*', sha1((str_pad($private_key, 64, chr(0x00)) ^ (str_repeat(chr(0x5c), 64))) . + pack('H*', sha1((str_pad($private_key, 64, chr(0x00)) ^ (str_repeat(chr(0x36), 64))) . + $message)))) + ); + return $hash; + } + _backup_migrate_message('You must enter a valid secret key to use NodeSquirrel.', array(), 'error'); + return FALSE; + } + + /** + * Genrate a hash with a given secret key, timestamp and random value. + */ + function _sign_request(&$args) { + $nonce = md5(mt_rand()); + $time = time(); + $hash = $this->_get_hash($time, $nonce); + if ($hash) { + array_unshift($args, $nonce); + array_unshift($args, $time); + array_unshift($args, $hash); + return TRUE; + } + else { + return FALSE; + } + } + + /** + * Retrieve the list of servers. + */ + function _get_endpoints($refresh = FALSE, $retry = 3) { + $servers = (array)variable_get('nodesquirrel_endpoint_urls', array()); + + // No servers saved or a force refreshr required. + if ($refresh || empty($servers)) { + $servers = array_unique(array_merge($servers, variable_get('nodesquirrel_default_endpoint_urls', array('api.nodesquirrel.com/services/xmlrpc')))); + // Call the get endpoints method but use the default or previous servers to avoid infinite loops. + $new_servers = $this->__xmlrpc('backups.getEndpoints', array($this->_get_destination(), 'xmlrpc'), $servers, $retry); + if ($new_servers) { + variable_set('nodesquirrel_endpoint_urls', $new_servers); + $servers = $new_servers; + } + } + return $servers; + } + + /** + * Post a file via http. + * + * This looks a lot like a clone of drupal_http_request but it can post a large + * file without reading the whole file into memory. + */ + function _post_file($url, $method = 'GET', $params = array(), $file = NULL, $retry = 3) { + global $db_prefix; + + $result = new stdClass(); + + // Parse the URL and make sure we can handle the schema. + $uri = parse_url($url); + + if ($uri == FALSE) { + $result->error = 'unable to parse URL'; + $result->code = -1001; + return $result; + } + + if (!isset($uri['scheme'])) { + $result->error = 'missing schema'; + $result->code = -1002; + return $result; + } + + switch ($uri['scheme']) { + case 'http': + case 'feed': + $port = isset($uri['port']) ? $uri['port'] : 80; + $host = $uri['host'] . ($port != 80 ? ':'. $port : ''); + $fp = @fsockopen($uri['host'], $port, $errno, $errstr, 15); + break; + case 'https': + // Note: Only works for PHP 4.3 compiled with OpenSSL. + $port = isset($uri['port']) ? $uri['port'] : 443; + $host = $uri['host'] . ($port != 443 ? ':'. $port : ''); + $fp = @fsockopen('ssl://'. $uri['host'], $port, $errno, $errstr, 20); + break; + default: + $result->error = 'invalid schema '. $uri['scheme']; + $result->code = -1003; + return $result; + } + + // Make sure the socket opened properly. + if (!$fp) { + // When a network error occurs, we use a negative number so it does not + // clash with the HTTP status codes. + $result->code = -$errno; + $result->error = trim($errstr); + + // Mark that this request failed. This will trigger a check of the web + // server's ability to make outgoing HTTP requests the next time that + // requirements checking is performed. + // @see system_requirements() + variable_set('drupal_http_request_fails', TRUE); + + return $result; + } + + // Construct the path to act on. + $path = isset($uri['path']) ? $uri['path'] : '/'; + if (isset($uri['query'])) { + $path .= '?'. $uri['query']; + } + + // Prepare the data payload. + $boundary = '---------------------------'. substr(md5(rand(0,32000)),0,10); + $data_footer = "\r\n--$boundary--\r\n"; + + $data_header = ''; + foreach ($params as $key => $value) { + $data_header .="--$boundary\r\n"; + $data_header .= "Content-Disposition: form-data; name=\"".$key."\"\r\n"; + $data_header .= "\r\n".$value."\r\n"; + $data_header .="--$boundary\r\n"; + } + + // Add the file header to the post payload. + $data_header .="--$boundary\r\n"; + $data_header .= "Content-Disposition: form-data; name=\"file\"; filename=\"". $file->filename() ."\"\r\n"; + $data_header .= "Content-Type: application/octet-stream;\r\n"; + $data_header .= "\r\n"; + + // Calculate the content length. + $content_length = strlen($data_header . $data_footer) + filesize($file->filepath()); + + //file_get_contents($file->filepath())); + + // Create HTTP request. + $defaults = array( + // RFC 2616: "non-standard ports MUST, default ports MAY be included". + // We don't add the port to prevent from breaking rewrite rules checking the + // host that do not take into account the port number. + 'Host' => "Host: $host", + 'Content-type' => "Content-type: multipart/form-data, boundary=$boundary", + 'User-Agent' => 'User-Agent: NodeSquirrel Client/1.x (+http://www.nodesquirrel.com) (Drupal '. VERSION .'; Backup and Migrate 2.x)', + 'Content-Length' => 'Content-Length: '. $content_length + ); + + // If the server url has a user then attempt to use basic authentication + if (isset($uri['user'])) { + $defaults['Authorization'] = 'Authorization: Basic '. base64_encode($uri['user'] . (!empty($uri['pass']) ? ":". $uri['pass'] : '')); + } + + $request = $method .' '. $path ." HTTP/1.0\r\n"; + $request .= implode("\r\n", $defaults); + $request .= "\r\n\r\n"; + $result->request = $request; + + // Write the headers and start of the headers + fwrite($fp, $request); + fwrite($fp, $data_header); + + // Copy the file 512k at a time to prevent memory issues. + if ($fp_in = fopen($file->filepath(), 'rb')) { + while (!feof($fp_in)) { + fwrite($fp, fread($fp_in, 1024 * 512)); + } + $success = TRUE; + } + @fclose($fp_in); + + // Finish the write. + fwrite($fp, $data_footer); + + // Fetch response. + $response = ''; + while (!feof($fp) && $chunk = fread($fp, 1024)) { + $response .= $chunk; + } + fclose($fp); + + if (variable_get('debug_http_request', FALSE)) { + drupal_debug(date('r')); + drupal_debug($request); + drupal_debug($response); + } + + // Parse response. + list($split, $result->data) = explode("\r\n\r\n", $response, 2); + $split = preg_split("/\r\n|\n|\r/", $split); + + list($protocol, $code, $status_message) = explode(' ', trim(array_shift($split)), 3); + $result->protocol = $protocol; + $result->status_message = $status_message; + + $result->headers = array(); + + // Parse headers. + while ($line = trim(array_shift($split))) { + list($header, $value) = explode(':', $line, 2); + if (isset($result->headers[$header]) && $header == 'Set-Cookie') { + // RFC 2109: the Set-Cookie response header comprises the token Set- + // Cookie:, followed by a comma-separated list of one or more cookies. + $result->headers[$header] .= ','. trim($value); + } + else { + $result->headers[$header] = trim($value); + } + } + + $responses = array( + 100 => 'Continue', 101 => 'Switching Protocols', + 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', + 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', + 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Time-out', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Large', 415 => 'Unsupported Media Type', 416 => 'Requested range not satisfiable', 417 => 'Expectation Failed', + 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Time-out', 505 => 'HTTP Version not supported' + ); + // RFC 2616 states that all unknown HTTP codes must be treated the same as the + // base code in their class. + if (!isset($responses[$code])) { + $code = floor($code / 100) * 100; + } + + switch ($code) { + case 200: // OK + case 304: // Not modified + break; + case 301: // Moved permanently + case 302: // Moved temporarily + case 307: // Moved temporarily + $location = $result->headers['Location']; + + if ($retry) { + $result = drupal_http_request($result->headers['Location'], $headers, $method, $data, --$retry); + $result->redirect_code = $result->code; + } + $result->redirect_url = $location; + + break; + default: + $result->error = $status_message; + } + + $result->code = $code; + return $result; + } + +} + diff --git a/sites/all/modules/contrib/backup_migrate/includes/destinations.s3.inc b/sites/all/modules/contrib/backup_migrate/includes/destinations.s3.inc new file mode 100644 index 0000000..c049806 --- /dev/null +++ b/sites/all/modules/contrib/backup_migrate/includes/destinations.s3.inc @@ -0,0 +1,184 @@ +s3_object()) { + $path = $file->filename(); + if ($s3->putObject($s3->inputFile($file->filepath(), FALSE), $this->get_bucket(), $this->remote_path($file->filename()), S3::ACL_PRIVATE)) { + return $file; + } + } + return FALSE; + } + + /** + * Load from the s3 destination. + */ + function load_file($file_id) { + backup_migrate_include('files'); + $file = new backup_file(array('filename' => $file_id)); + if ($s3 = $this->s3_object()) { + $data = $s3->getObject($this->get_bucket(), $this->remote_path($file_id), $file->filepath()); + if (!$data->error) { + return $file; + } + } + return NULL; + } + + /** + * Delete from the s3 destination. + */ + function _delete_file($file_id) { + if ($s3 = $this->s3_object()) { + $s3->deleteObject($this->get_bucket(), $this->remote_path($file_id)); + } + } + + /** + * List all files from the s3 destination. + */ + function _list_files() { + backup_migrate_include('files'); + $files = array(); + if ($s3 = $this->s3_object()) { + $s3_files = $s3->getBucket($this->get_bucket(), $this->get_subdir()); + foreach ((array)$s3_files as $id => $file) { + $info = array( + 'filename' => $this->local_path($file['name']), + 'filesize' => $file['size'], + 'filetime' => $file['time'], + ); + $files[$info['filename']] = new backup_file($info); + } + } + return $files; + } + + /** + * Get the form for the settings for this filter. + */ + function edit_form() { + // Check for the library. + $this->s3_object(); + + $form = parent::edit_form(); + $form['scheme']['#type'] = 'value'; + $form['scheme']['#value'] = 'https'; + $form['host']['#type'] = 'value'; + $form['host']['#value'] = 's3.amazonaws.com'; + + $form['path']['#title'] = 'S3 Bucket'; + $form['path']['#default_value'] = $this->get_bucket(); + $form['path']['#description'] = 'This bucket must already exist. It will not be created for you.'; + + $form['user']['#title'] = 'Access Key ID'; + $form['pass']['#title'] = 'Secret Access Key'; + + $form['subdir'] = array( + '#type' => 'textfield', + '#title' => t('Subdirectory'), + '#default_value' => $this->get_subdir(), + '#weight' => 25 + ); + $form['settings']['#weight'] = 50; + + return $form; + } + + /** + * Submit the form for the settings for the s3 destination. + */ + function edit_form_submit($form, &$form_state) { + // Append the subdir onto the path. + + if (!empty($form_state['values']['subdir'])) { + $form_state['values']['path'] .= '/'. trim($form_state['values']['subdir'], '/'); + } + parent::edit_form_submit($form, $form_state); + } + + /** + * Generate a filepath with the correct prefix. + */ + function remote_path($path) { + if ($subdir = $this->get_subdir()) { + $path = $subdir .'/'. $path; + } + return $path; + } + + /** + * Generate a filepath with the correct prefix. + */ + function local_path($path) { + if ($subdir = $this->get_subdir()) { + $path = str_replace($subdir .'/', '', $path); + } + return $path; + } + + /** + * Get the bucket which is the first part of the path. + */ + function get_bucket() { + $parts = explode('/', @$this->dest_url['path']); + return $parts[0]; + } + + /** + * Get the bucket which is the first part of the path. + */ + function get_subdir() { + // Support the older style of subdir saving. + if ($subdir = $this->settings('subdir')) { + return $subdir; + } + $parts = explode('/', @$this->dest_url['path']); + array_shift($parts); + return implode('/', array_filter($parts)); + } + + function s3_object() { + // Try to use libraries module if available to find the path. + if (function_exists('libraries_get_path')) { + $library_paths[] = libraries_get_path('s3-php5-curl'); + } + else { + $library_paths[] = 'sites/all/libraries/s3-php5-curl'; + } + $library_paths[] = drupal_get_path('module', 'backup_migrate') . '/includes/s3-php5-curl'; + $library_paths[] = drupal_get_path('module', 'backup_migrate') . '/includes'; + + foreach($library_paths as $path) { + if (file_exists($path . '/S3.php')) { + require_once $path . '/S3.php'; + if (!$this->s3 && !empty($this->dest_url['user'])) { + $this->s3 = new S3($this->dest_url['user'], $this->dest_url['pass']); + } + return $this->s3; + } + } + drupal_set_message(t('Due to drupal.org code hosting policies, the S3 library needed to use an S3 destination is no longer distributed with this module. You must download the library from !link and place it in one of these locations: %locations.', array('%locations' => implode(', ', $library_paths), '!link' => l('http://undesigned.org.za/2007/10/22/amazon-s3-php-class', 'http://undesigned.org.za/2007/10/22/amazon-s3-php-class'))), 'error', FALSE); + return NULL; + } +} diff --git a/sites/all/modules/contrib/backup_migrate/includes/files.inc b/sites/all/modules/contrib/backup_migrate/includes/files.inc new file mode 100644 index 0000000..8931189 --- /dev/null +++ b/sites/all/modules/contrib/backup_migrate/includes/files.inc @@ -0,0 +1,564 @@ + $tmp), 'error'); + } + + // Use a full path so that the files can be deleted during the shutdown function if needed. + $file = $tmp .'/'. uniqid('backup_migrate_'); + mkdir($file); + backup_migrate_temp_files_add($file); + return $file; +} + +/** + * Return a list of backup filetypes. + */ +function _backup_migrate_filetypes() { + backup_migrate_include('filters'); + + $out = backup_migrate_filters_file_types(); + + foreach ($out as $key => $info) { + $out[$key]['id'] = empty($info['id']) ? $key : $info['id']; + } + return $out; +} + +/** + * Adjust the length of a filename to allow for a string to be appended, + * staying within the maximum filename limit. + */ +function _backup_migrate_filename_append_prepare($filename, $append_str) { + $max_name_len = BACKUP_MIGRATE_FILENAME_MAXLENGTH - drupal_strlen($append_str); + if (drupal_strlen($filename) > $max_name_len) { + $filename = drupal_substr($filename, 0, $max_name_len); + } + return $filename; +} + +/** + * Construct a filename using token and some cleaning. + */ +function _backup_migrate_construct_filename($settings) { + // Get the raw filename from the settings. + $filename = $settings->filename; + + // Replace any tokens + if (module_exists('token') && function_exists('token_replace')) { + $filename = token_replace($filename); + } + + // Remove illegal characters + $filename = _backup_migrate_clean_filename($filename); + + // Generate a timestamp if needed. + $timestamp = ''; + if ($settings->append_timestamp && $settings->timestamp_format) { + $timestamp = format_date(time(), 'custom', $settings->timestamp_format); + } + + // Trim to length if needed to allow the timestamp to fit into the max filename. + $filename = _backup_migrate_filename_append_prepare($filename, $timestamp); + $filename .= '-' . $timestamp; + $filename = trim($filename, '-'); + + // If there is no filename, then just call it untitled. + if (drupal_strlen($filename) == 0) { + $filename = 'untitled'; + } + return $filename; +} + +/** + * Construct a default filename using the site's name. + */ +function _backup_migrate_default_filename() { + if (module_exists('token')) { + return '[site:name]'; + } + else { + // Cleaning the string isn't strictly necessary but it looks better in the settings field. + return _backup_migrate_clean_filename(variable_get('site_name', "backup_migrate")); + } +} + +/** + * Clean up a filename to remove unsafe characters. + */ +function _backup_migrate_clean_filename($filename) { + $filename = preg_replace("/[^a-zA-Z0-9\.\-_]/", "", $filename); + return $filename; +} + +/** + * An output buffer callback which simply throws away the buffer instead of sending it to the browser. + */ +function _backup_migrate_file_dispose_buffer($buffer) { + return ""; +} + + +/** + * A backup file which allows for saving to and reading from the server. + */ +class backup_file { + var $file_info = array(); + var $type = array(); + var $ext = array(); + var $path = ""; + var $name = ""; + var $handle = NULL; + + /** + * Construct a file object given a file path, or create a temp file for writing. + */ + function backup_file($params = array()) { + if (isset($params['filepath']) && file_exists($params['filepath'])) { + $this->set_filepath($params['filepath']); + } + else { + $this->set_file_info($params); + $this->temporary_file(); + } + } + + /** + * Get the file_id if the file has been saved to a destination. + */ + function file_id() { + // The default file_id is the filename. Destinations can override the file_id if needed. + return isset($this->file_info['file_id']) ? $this->file_info['file_id'] : $this->filename(); + } + + /** + * Get the current filepath. + */ + function filepath() { + return drupal_realpath($this->path); + } + + /** + * Get the final filename. + */ + function filename($name = NULL) { + if ($name) { + $this->name = $name; + } + $extension_str = '.' . $this->extension(); + $this->name = _backup_migrate_filename_append_prepare($this->name, $extension_str); + return $this->name . $extension_str; + } + + /** + * Set the current filepath. + */ + function set_filepath($path) { + $this->path = $path; + $params = array( + 'filename' => basename($path), + 'file_id' => basename($path) + ); + if (file_exists($path)) { + $params['filesize'] = filesize($path); + $params['filetime'] = filectime($path); + } + $this->set_file_info($params); + } + + /** + * Get one or all pieces of info for the file. + */ + function info($key = NULL) { + if ($key) { + return @$this->file_info[$key]; + } + return $this->file_info; + } + + + /** + * Get one or all pieces of info for the file. + */ + function info_set($key, $value) { + $this->file_info[$key] = $value; + } + /** + * Get the file extension. + */ + function extension() { + return implode(".", $this->ext); + } + + /** + * Get the file type. + */ + function type() { + return $this->type; + } + + /** + * Get the file mimetype. + */ + function mimetype() { + return @$this->type['filemime'] ? $this->type['filemime'] : 'application/octet-stream'; + } + + /** + * Get the file mimetype. + */ + function type_id() { + return @$this->type['id']; + } + + function filesize() { + if (empty($this->file_info['filesize'])) { + $this->calculate_filesize(); + } + return $this->file_info['filesize']; + } + + function calculate_filesize() { + $this->file_info['filesize'] = ''; + if (!empty($this->path) && file_exists($this->path)) { + $this->file_info['filesize'] = filesize($this->path); + } + } + + /** + * Can this file be used to backup to. + */ + function can_backup() { + return @$this->type['backup']; + } + + /** + * Can this file be used to restore to. + */ + function can_restore() { + return @$this->type['restore']; + } + + /** + * Can this file be used to restore to. + */ + function is_recognized_type() { + return @$this->type['restore'] || @$this->type['backup']; + } + + /** + * Open a file for reading or writing. + */ + function open($write = FALSE, $binary = FALSE) { + if (!$this->handle) { + $path = $this->filepath(); + + // Check if the file can be read/written. + if ($write && ((file_exists($path) && !is_writable($path)) || !is_writable(dirname($path)))) { + _backup_migrate_message('The file %path cannot be written to.', array('%path' => $path), 'error'); + return FALSE; + } + if (!$write && !is_readable($path)) { + _backup_migrate_message('The file %path cannot be read.', array('%path' => $path), 'error'); + return FALSE; + } + + // Open the file. + $mode = ($write ? "w" : "r") . ($binary ? "b" : ""); + $this->handle = fopen($path, $mode); + return $this->handle; + } + return NULL; + } + + /** + * Close a file when we're done reading/writing. + */ + function close() { + fclose($this->handle); + $this->handle = NULL; + } + + /** + * Write a line to the file. + */ + function write($data) { + if (!$this->handle) { + $this->handle = $this->open(TRUE); + } + if ($this->handle) { + fwrite($this->handle, $data); + } + } + + /** + * Read a line from the file. + */ + function read($size = NULL) { + if (!$this->handle) { + $this->handle = $this->open(); + } + if ($this->handle && !feof($this->handle)) { + return $size ? fread($this->handle, $size) : fgets($this->handle); + } + return NULL; + } + + /** + * Write data to the file. + */ + function put_contents($data) { + file_put_contents($this->filepath(), $data); + } + + /** + * Read data from the file. + */ + function get_contents() { + return file_get_contents($this->filepath()); + } + + /** + * Transfer file using http to client. Similar to the built in file_transfer, + * but it calls module_invoke_all('exit') so that temp files can be deleted. + */ + function transfer() { + $headers = array( + array('key' => 'Content-Type', 'value' => $this->mimetype()), + array('key' => 'Content-Disposition', 'value' => 'attachment; filename="'. $this->filename() .'"'), + ); + // In some circumstances, web-servers will double compress gzipped files. + // This may help aleviate that issue by disabling mod-deflate. + if ($this->mimetype() == 'application/x-gzip') { + $headers[] = array('key' => 'Content-Encoding', 'value' => 'gzip'); + } + if ($size = $this->info('filesize')) { + $headers[] = array('key' => 'Content-Length', 'value' => $size); + } + + // Suppress the warning you get when the buffer is empty. + @ob_end_clean(); + + if ($this->open(FALSE, TRUE)) { + foreach ($headers as $header) { + // To prevent HTTP header injection, we delete new lines that are + // not followed by a space or a tab. + // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + $header['value'] = preg_replace('/\r?\n(?!\t| )/', '', $header['value']); + drupal_add_http_header($header['key'], $header['value']); + } + // Transfer file in 1024 byte chunks to save memory usage. + while ($data = $this->read(1024)) { + print $data; + } + $this->close(); + + // Ask devel.module not to print it's footer. + $GLOBALS['devel_shutdown'] = FALSE; + } + else { + drupal_not_found(); + } + + // Start buffering and throw away the results so that errors don't get appended to the file. + ob_start('_backup_migrate_file_dispose_buffer'); + backup_migrate_cleanup(); + module_invoke_all('exit'); + exit(); + } + + /** + * Push a file extension onto the file and return the previous file path. + */ + function push_type($extension) { + $types = _backup_migrate_filetypes(); + if ($type = @$types[$extension]) { + $this->push_filetype($type); + } + + $out = $this->filepath(); + $this->temporary_file(); + return $out; + } + + /** + * Push a file extension onto the file and return the previous file path. + */ + function pop_type() { + $out = new backup_file(array('filepath' => $this->filepath())); + $this->pop_filetype(); + $this->temporary_file(); + return $out; + } + + /** + * Set the current file type. + */ + function set_filetype($type) { + $this->type = $type; + $this->ext = array($type['extension']); + } + + /** + * Set the current file type. + */ + function push_filetype($type) { + $this->ext[] = $type['extension']; + $this->type = $type; + } + + /** + * Pop the current file type. + */ + function pop_filetype() { + array_pop($this->ext); + $this->detect_filetype_from_extension(); + } + + /** + * Set the file info. + */ + function set_file_info($file_info) { + $this->file_info = $file_info; + + $this->ext = explode('.', @$this->file_info['filename']); + // Remove the underscores added to file extensions by Drupal's upload security. + foreach ($this->ext as $key => $val) { + $this->ext[$key] = trim($val, '_'); + } + $this->filename(array_shift($this->ext)); + $this->detect_filetype_from_extension(); + } + + /** + * Get the filetype info of the given file, or false if the file is not a valid type. + */ + function detect_filetype_from_extension() { + $ext = end($this->ext); + $this->type = array(); + $types = _backup_migrate_filetypes(); + foreach ($types as $key => $type) { + if (trim($ext, "_0123456789") === $type['extension']) { + $this->type = $type; + $this->type['id'] = $key; + } + } + } + + /** + * Get a temporary file name with path. + */ + function temporary_file() { + $file = drupal_tempnam('temporary://', 'backup_migrate_'); + // Add the version without the extension. The tempnam function creates this for us. + backup_migrate_temp_files_add($file); + + if ($this->extension()) { + $file .= '.'. $this->extension(); + // Add the version with the extension. This is the one we will actually use. + backup_migrate_temp_files_add($file); + } + $this->path = $file; + } +} + diff --git a/sites/all/modules/contrib/backup_migrate/includes/filters.backup_restore.inc b/sites/all/modules/contrib/backup_migrate/includes/filters.backup_restore.inc new file mode 100644 index 0000000..cf80b59 --- /dev/null +++ b/sites/all/modules/contrib/backup_migrate/includes/filters.backup_restore.inc @@ -0,0 +1,249 @@ + 0, 'restore' => 0); + + /** + * Get the default destinations for this filter. + */ + function destinations() { + $out = array(); + foreach ($this->_get_destination_types() as $destination) { + if (method_exists($destination, 'destinations')) { + $out += $destination->destinations(); + } + } + return $out; + } + + /** + * Get the default sources for this filter. + */ + function sources() { + $out = array(); + foreach ($this->_get_source_types() as $type) { + if (method_exists($type, 'sources')) { + $out += $type->sources(); + } + } + return $out; + } + + /** + * Get the default backup settings for this filter. + */ + function backup_settings_default() { + backup_migrate_include('sources'); + $out = array(); + foreach (backup_migrate_get_sources() as $source) { + $out['sources'][$source->get_id()] = $source->backup_settings_default(); + } + return $out; + } + + /** + * Get the form for the settings for this filter. + */ + function backup_settings_form_validate($form, &$form_state) { + foreach ($this->_get_destination_types() as $destination) { + $destination->backup_settings_form_validate($form, $form_state); + } + } + + /** + * Submit the settings form. Any values returned will be saved. + */ + function backup_settings_form_submit($form, &$form_state) { + foreach ($this->_get_destination_types() as $destination) { + $destination->backup_settings_form_submit($form, $form_state); + } + } + + /** + * Get the default restore settings for this filter. + */ + function restore_settings_default() { + $out = array(); + foreach ($this->_get_destination_types() as $destination) { + $out += $destination->restore_settings_default(); + } + return $out; + } + + /** + * Get the form for the backup settings for this filter. + */ + function backup_settings_form($settings) { + backup_migrate_include('sources'); + $out = array('sources' => array( + '#tree' => TRUE, + )); + foreach (backup_migrate_get_sources() as $source) { + $source_settings = (array)(@$settings['sources'][$source->get_id()]) + $settings; + if ($form = $source->backup_settings_form($source_settings)) { + $out['sources'][$source->get_id()] = array( + '#type' => 'fieldset', + '#title' => t('!name Backup Options', array('!name' => $source->get('name'))), + "#collapsible" => TRUE, + "#collapsed" => TRUE, + '#tree' => TRUE, + '#parents' => array('filters', 'sources', $source->get_id()), + ) + $form; + } + } + return $out; + } + + /** + * Get the form for the restore settings for this filter. + */ + function restore_settings_form($settings) { + $form = array(); + foreach ($this->_get_destination_types() as $destination) { + $destination->restore_settings_form($form, $settings); + } + return $form; + } + + /** + * Get the before-backup form for the active sources and destinations. + */ + function before_action_form($op, $settings) { + $form = array(); + $method = 'before_' . $op . '_form'; + if ($source = $settings->get_source()) { + if (method_exists($source, $method)) { + $form += $source->{$method}($settings); + } + } + foreach ($settings->get_destinations() as $destination) { + if (method_exists($destination, $method)) { + $form += $destination->{$method}($settings); + } + } + return $form; + } + + /** + * Get the before-backup form for the active sources and destinations. + */ + function before_action_form_validate($op, $settings, $form, $form_state) { + $method = 'before_' . $op . '_form_validate'; + foreach ($settings->get_all_locations() as $location) { + if (method_exists($location, $method)) { + $location->{$method}($settings, $form, $form_state); + } + } + } + + /** + * Get the before-backup form for the active sources and destinations. + */ + function before_action_form_submit($op, $settings, $form, $form_state) { + $method = 'before_' . $op . '_form_submit'; + foreach ($settings->get_all_locations() as $location) { + if (method_exists($location, $method)) { + $location->{$method}($settings, $form, $form_state); + } + } + } + + /** + * Get the file types supported by this destination. + */ + function file_types() { + $types = array(); + foreach ($this->_get_destination_types() as $destination) { + $types += $destination->file_types(); + } + foreach ($this->_get_source_types() as $source) { + $types += $source->file_types(); + } + return $types; + } + + /** + * Backup the data from the source specified in the settings. + */ + function backup($file, $settings) { + if ($source = $settings->get_source()) { + if (!empty($settings->filters['sources'][$source->get_id()])) { + $settings->filters = (array)($settings->filters['sources'][$source->get_id()]) + $settings->filters; + } + + $file = $source->backup_to_file($file, $settings); + return $file; + } + backup_migrate_backup_fail("Could not run backup because the source '%source' is missing.", array('%source' => $settings->source_id), $settings); + return FALSE; + } + + /** + * Restore the data from to source specified in the settings. + */ + function restore($file, $settings) { + if ($source = $settings->get_source()) { + if (!empty($settings->filters['sources'][$source->get_id()])) { + $settings->filters = (array)($settings->filters['sources'][$source->get_id()]) + $settings->filters; + } + $num = $source->restore_from_file($file, $settings); + return $num ? $file : FALSE; + } + backup_migrate_restore_fail("Could not run restore because the source '%source' is missing.", array('%source' => $settings->source_id), $settings); + return FALSE; + } + + /** + * Get a list of dummy destinations representing each of the available destination types. + */ + function _get_destination_types() { + backup_migrate_include('destinations'); + static $destinations = NULL; + if (!is_array($destinations)) { + $destinations = array(); + $types = backup_migrate_get_destination_subtypes(); + // If no (valid) node type has been provided, display a node type overview. + foreach ($types as $key => $type) { + // Include the necessary file if specified by the type. + if (!empty($type['file'])) { + require_once './'. $type['file']; + } + $destinations[] = new $type['class'](array()); + } + } + return $destinations; + } + + /** + * Get a list of dummy destinations representing each of the available source types. + */ + function _get_source_types() { + backup_migrate_include('sources'); + static $sources = NULL; + if (!is_array($sources)) { + $sources = array(); + $types = backup_migrate_get_source_subtypes(); + // If no (valid) node type has been provided, display a node type overview. + foreach ($types as $key => $type) { + // Include the necessary file if specified by the type. + if (!empty($type['file'])) { + require_once './'. $type['file']; + } + $sources[] = new $type['class'](array()); + } + } + return $sources; + } + +} diff --git a/sites/all/modules/contrib/backup_migrate/includes/filters.compression.inc b/sites/all/modules/contrib/backup_migrate/includes/filters.compression.inc new file mode 100644 index 0000000..e399d29 --- /dev/null +++ b/sites/all/modules/contrib/backup_migrate/includes/filters.compression.inc @@ -0,0 +1,298 @@ + 100, 'restore' => -100); + + /** + * This function is called on a backup file after the backup has been completed. + */ + function backup($file, $settings) { + return $this->_backup_migrate_file_compress($file, $settings); + } + + /** + * This function is called on a backup file before importing it. + */ + function restore($file, $settings) { + return $this->_backup_migrate_file_decompress($file, $settings); + } + + /** + * Get the form for the settings for this filter. + */ + function backup_settings_default() { + $options = $this->_backup_migrate_get_compression_form_item_options(); + return array('compression' => isset($options['gzip']) ? 'gzip' : 'none'); + } + + /** + * Get the form for the settings for this filter. + */ + function backup_settings_form($settings) { + $form = array(); + $compression_options = $this->_backup_migrate_get_compression_form_item_options(); + $form['file']['compression'] = array( + "#type" => count($compression_options) > 1 ? "select" : 'value', + "#title" => t("Compression"), + "#options" => $compression_options, + "#default_value" => $settings['compression'], + ); + return $form; + } + + /** + * Return a list of backup filetypes. + */ + function file_types() { + return array( + "gzip" => array( + "extension" => "gz", + "filemime" => "application/x-gzip", + "backup" => TRUE, + "restore" => TRUE, + ), + "bzip" => array( + "extension" => "bz", + "filemime" => "application/x-bzip", + "backup" => TRUE, + "restore" => TRUE, + ), + "bzip2" => array( + "extension" => "bz2", + "filemime" => "application/x-bzip", + "backup" => TRUE, + "restore" => TRUE, + ), + "zip" => array( + "extension" => "zip", + "filemime" => "application/zip", + "backup" => TRUE, + "restore" => TRUE, + ), + ); + } + + /** + * Get the compression options as an options array for a form item. + */ + function _backup_migrate_get_compression_form_item_options() { + $compression_options = array("none" => t("No Compression")); + if (@function_exists("gzencode")) { + $compression_options['gzip'] = t("GZip"); + } + if (@function_exists("bzcompress")) { + $compression_options['bzip'] = t("BZip"); + } + if (class_exists('ZipArchive')) { + $compression_options['zip'] = t("Zip", array(), array('context' => 'compression format')); + } + return $compression_options; + } + + /** + * Gzip encode a file. + */ + function _backup_migrate_gzip_encode($source, $dest, $level = 9, $settings) { + $success = FALSE; + // Try command line gzip first. + + if (!empty($settings->filters['use_cli'])) { + $success = backup_migrate_exec("gzip -c -$level %input > %dest", array('%input' => $source, '%dest' => $dest, '%level' => $level)); + } + if (!$success && @function_exists("gzopen")) { + if (($fp_out = gzopen($dest, 'wb'. $level)) && ($fp_in = fopen($source, 'rb'))) { + while (!feof($fp_in)) { + gzwrite($fp_out, fread($fp_in, 1024 * 512)); + } + $success = TRUE; + } + @fclose($fp_in); + @gzclose($fp_out); + } + return $success; + } + + /** + * Gzip decode a file. + */ + function _backup_migrate_gzip_decode($source, $dest, $settings) { + $success = FALSE; + + if (!empty($settings->filters['use_cli'])) { + $success = backup_migrate_exec("gzip -d -c %input > %dest", array('%input' => $source, '%dest' => $dest)); + } + + if (!$success && @function_exists("gzopen")) { + if (($fp_out = fopen($dest, 'wb')) && ($fp_in = gzopen($source, 'rb'))) { + while (!feof($fp_in)) { + fwrite($fp_out, gzread($fp_in, 1024 * 512)); + } + $success = TRUE; + } + @gzclose($fp_in); + @fclose($fp_out); + } + return $success; + } + + /** + * Bzip encode a file. + */ + function _backup_migrate_bzip_encode($source, $dest) { + $success = FALSE; + if (@function_exists("bzopen")) { + if (($fp_out = bzopen($dest, 'w')) && ($fp_in = fopen($source, 'rb'))) { + while (!feof($fp_in)) { + bzwrite($fp_out, fread($fp_in, 1024 * 512)); + } + $success = TRUE; + } + else { + $error = TRUE; + } + @fclose($fp_in); + @bzclose($fp_out); + } + return $success; + } + + /** + * Bzip decode a file. + */ + function _backup_migrate_bzip_decode($source, $dest) { + $success = FALSE; + if (@function_exists("bzopen")) { + if (($fp_out = fopen($dest, 'w')) && ($fp_in = bzopen($source, 'r'))) { + while (!feof($fp_in)) { + fwrite($fp_out, gzread($fp_in, 1024 * 512)); + } + $success = TRUE; + } + else { + $error = TRUE; + } + @bzclose($fp_in); + @fclose($fp_out); + } + return $success; + } + + /** + * Zip encode a file. + */ + function _backup_migrate_zip_encode($source, $dest, $filename) { + $success = FALSE; + if (class_exists('ZipArchive')) { + $zip = new ZipArchive; + $res = $zip->open($dest, constant("ZipArchive::CREATE")); + if ($res === TRUE) { + $zip->addFile($source, $filename); + $success = $zip->close(); + } + } + return $success; + } + + /** + * Zip decode a file. + */ + function _backup_migrate_zip_decode($source, $dest) { + $success = FALSE; + if (class_exists('ZipArchive')) { + $zip = new ZipArchive; + if (($fp_out = fopen($dest, "w")) && ($zip->open($source))) { + $filename = ($zip->getNameIndex(0)); + if ($fp_in = $zip->getStream($filename)) { + while (!feof($fp_in)) { + fwrite($fp_out, fread($fp_in, 1024 * 512)); + } + $success = TRUE; + } + } + @fclose($fp_in); + @fclose($fp_out); + } + return $success; + } + + /** + * Compress a file with the given settings. + * Also updates settings to reflect new file mime and file extension. + */ + function _backup_migrate_file_compress($file, $settings) { + switch ($settings->filters['compression']) { + case "gzip": + $from = $file->push_type('gzip'); + if (!$success = $this->_backup_migrate_gzip_encode($from, $file->filepath(), 9, $settings)) { + $file = NULL; + } + break; + + case "bzip": + $from = $file->push_type('bzip2'); + if (!$success = $this->_backup_migrate_bzip_encode($from, $file->filepath())) { + $file = NULL; + } + break; + + case "zip": + $filename = $file->filename(); + $from = $file->push_type('zip'); + if (!$success = $this->_backup_migrate_zip_encode($from, $file->filepath(), $filename)) { + $file = NULL; + } + break; + } + if (!$file) { + _backup_migrate_message("Could not compress backup file. Try backing up without compression.", array(), 'error'); + } + return $file; + } + + /** + * Decompress a file with the given settings. + * Also updates settings to reflect new file mime and file extension. + */ + function _backup_migrate_file_decompress($file, $settings) { + $success = FALSE; + + switch ($file->type_id()) { + case "gzip": + $from = $file->pop_type(); + $success = $this->_backup_migrate_gzip_decode($from->filepath(), $file->filepath(), $settings); + break; + + case "bzip": + case "bzip2": + $from = $file->pop_type(); + $success = $this->_backup_migrate_bzip_decode($from->filepath(), $file->filepath()); + break; + + case "zip": + $from = $file->pop_type(); + $success = $this->_backup_migrate_zip_decode($from->filepath(), $file->filepath()); + break; + + default: + return $file; + break; + } + + if (!$success) { + _backup_migrate_message("Could not decompress backup file. Please check that the file is valid.", array(), 'error'); + } + return $success ? $file : NULL; + } +} + diff --git a/sites/all/modules/contrib/backup_migrate/includes/filters.encryption.inc b/sites/all/modules/contrib/backup_migrate/includes/filters.encryption.inc new file mode 100644 index 0000000..74b679c --- /dev/null +++ b/sites/all/modules/contrib/backup_migrate/includes/filters.encryption.inc @@ -0,0 +1,177 @@ + 170, 'restore' => -170); + + /** + * This function is called on a backup file after the backup has been completed. + */ + function backup($file, $settings) { + return $this->file_encrypt($file, $settings); + } + + /** + * This function is called on a backup file before importing it. + */ + function restore($file, $settings) { + return $this->file_decrypt($file); + } + + /** + * Get the form for the settings for this filter. + */ + function backup_settings_default() { + return array('encryption' => 'none'); + } + + /** + * Get the form for the settings for this filter. + */ + function backup_settings_form($settings) { + $form = array(); + $options = $this->_backup_migrate_get_encryption_form_item_options(); + if (count($options) > 1) { + $form['file']['encryption'] = array( + "#type" => "select", + "#title" => t("File Encryption"), + "#options" => $options, + "#default_value" => @$settings['encryption'], + '#description' => t('Encrypted files can only be restored by Backup and Migrate and only on sites with the same encryption key.'), + ); + } + else { + $form['file']['encryption'] = array( + "#type" => 'item', + "#title" => t("File Encryption"), + "#markup" => t('Install the !link to enable backup file encryption.', array('!link' => l(t('AES Encryption Module'), 'http://drupal.org/project/aes'))), + ); + } + + return $form; + } + + /** + * Return a list of backup filetypes. + */ + function file_types() { + return array( + "aes" => array( + "extension" => "aes", + "filemime" => "application/octet-stream", + "backup" => TRUE, + "restore" => TRUE, + ), + ); + } + + /** + * Get the compression options as an options array for a form item. + */ + function _backup_migrate_get_encryption_form_item_options() { + $options = array(); + $options = array('' => t('No Encryption')); + if (@function_exists("aes_encrypt")) { + $options['aes'] = t("AES Encryption"); + } + return $options; + } + + /** + * AES encrypt a file. + */ + function aes_encrypt($source, $dest) { + $success = FALSE; + if (function_exists('aes_encrypt')) { + if ($data = $source->get_contents()) { + // Add a marker to the end of the data so we can trim the padding on decrpypt. + $data = pack("a*H2", $data, "80"); + if ($data = aes_encrypt($data, FALSE)) { + $dest->put_contents($data); + $success = TRUE; + } + } + } + return $success; + } + + /** + * Gzip decode a file. + */ + function aes_decrypt($source, $dest) { + $success = FALSE; + if (function_exists('aes_decrypt')) { + if ($data = $source->get_contents()) { + if ($data = aes_decrypt($data, FALSE)) { + // Trim all the padding zeros plus our non-zero marker. + $data = substr(rtrim($data, "\0"), 0, -1); + $dest->put_contents($data); + $success = TRUE; + } + } + } + return $success; + } + + /** + * Compress a file with the given settings. + * Also updates settings to reflect new file mime and file extension. + */ + function file_encrypt($file, $settings) { + if (!empty($settings->filters['encryption'])) { + switch ($settings->filters['encryption']) { + case "aes": + $from = $file->push_type('aes'); + $from = new backup_file(array('filepath' => $from)); + if (!$success = $this->aes_encrypt($from, $file)) { + $file = NULL; + } + break; + } + if (!$file) { + _backup_migrate_message("Could not encrypt backup file. Try backing up without encryption.", array(), 'error'); + } + } + return $file; + } + + /** + * Decompress a file with the given settings. + * Also updates settings to reflect new file mime and file extension. + */ + function file_decrypt($file) { + $success = FALSE; + if ($file) { + switch ($file->type_id()) { + case "aes": + $from = $file->pop_type(); + $success = $this->aes_decrypt($from, $file); + break; + default: + return $file; + break; + } + + if (!$success) { + if (function_exists('aes_decrypt')) { + _backup_migrate_message("Could not decrpyt backup file. Please check that the file is valid and that the encryption key of the server matches the server that created the backup.", array(), 'error'); + } + else { + _backup_migrate_message('You must install the !link to restore encrypted backkups.', array('!link' => l(t('AES Encryption Module'), 'http://drupal.org/project/aes')), 'error'); + } + } + } + return $success ? $file : NULL; + } +} + diff --git a/sites/all/modules/contrib/backup_migrate/includes/filters.inc b/sites/all/modules/contrib/backup_migrate/includes/filters.inc new file mode 100644 index 0000000..d0ed9ec --- /dev/null +++ b/sites/all/modules/contrib/backup_migrate/includes/filters.inc @@ -0,0 +1,332 @@ +weight($op); + } + array_multisort($sort, SORT_ASC, SORT_NUMERIC, $filters); + return $filters; +} + +/** + * Implementation of hook_backup_migrate_filters(). + * + * Get the built in Backup and Migrate filters. + */ +function backup_migrate_backup_migrate_filters() { + return array( + 'backup_restore' => array( + 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/filters.backup_restore.inc', + 'class' => 'backup_migrate_filter_backup_restore', + ), + 'compression' => array( + 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/filters.compression.inc', + 'class' => 'backup_migrate_filter_compression', + ), + 'encryption' => array( + 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/filters.encryption.inc', + 'class' => 'backup_migrate_filter_encryption', + ), + 'statusnotify' => array( + 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/filters.statusnotify.inc', + 'class' => 'backup_migrate_filter_statusnotify', + ), + 'utils' => array( + 'file' => drupal_get_path('module', 'backup_migrate') .'/includes/filters.utils.inc', + 'class' => 'backup_migrate_filter_utils', + ), + ); +} + +/** + * Invoke the given method on all of the available filters. + */ +function backup_migrate_filters_invoke_all() { + $args = func_get_args(); + $op = array_shift($args); + $out = array(); + $filters = backup_migrate_get_filters($op); + foreach ($filters as $filter) { + if (method_exists($filter, $op)) { + /* call_user_func_array() ignores the function signature, so we cannot + * use it to pass references. (Call-time pass-by-reference is deprecated + * in PHP5.3.) Work around it, since we have unknown function signatures. + */ + switch (count($args)) { + case 1: + $ret = $filter->$op($args[0]); + break; + + case 2: + $ret = $filter->$op($args[0], $args[1]); + break; + + default: + // This assumes that no functions with more than 2 arguments expect a + // reference as argument. If so, add another 'case block'. + $ret = call_user_func_array(array($filter, $op), $args); + } + $out = array_merge_recursive($out, (array) $ret); + } + } + return $out; +} + +/** + * Filter a backup file before sending it to the destination. + */ +function backup_migrate_filters_backup($file, &$settings) { + backup_migrate_filters_invoke_all('pre_backup', $file, $settings); + $filters = backup_migrate_get_filters('backup'); + foreach ($filters as $filter) { + if ($file) { + $file = $filter->backup($file, $settings); + } + } + backup_migrate_filters_invoke_all('post_backup', $file, $settings); + + return $file; +} + +/** + * Filter a backup file before sending it to the destination. + */ +function backup_migrate_filters_restore($file, &$settings) { + backup_migrate_filters_invoke_all('pre_restore', $file, $settings); + $filters = backup_migrate_get_filters('restore'); + foreach ($filters as $filter) { + if ($file) { + $file = $filter->restore($file, $settings); + } + } + backup_migrate_filters_invoke_all('post_restore', $file, $settings); + return $file; +} + +/** + * Get the backup settings for all of the filters. + */ +function backup_migrate_filters_settings_form($settings, $op) { + $out = backup_migrate_filters_invoke_all($op .'_settings_form', $settings); + $out = backup_migrate_filters_settings_form_set_parents($out); + return $out; +} + +/** + * Add a form parent to the filter settings so that the filter values are saved in the right table. + */ +function backup_migrate_filters_settings_form_set_parents($form) { + foreach (element_children($form) as $key) { + if (!isset($form[$key]['#parents'])) { + $form[$key]['#parents'] = array('filters', $key); + $form[$key] = backup_migrate_filters_settings_form_set_parents($form[$key]); + } + } + return $form; +} + +/** + * Validate all the filters. + */ +function backup_migrate_filters_settings_form_validate($op, $form, &$form_state) { + backup_migrate_filters_invoke_all($op .'_settings_form_validate', $form, $form_state); +} + +/** + * Submit all of the filters. + */ +function backup_migrate_filters_settings_form_submit($op, $form, &$form_state) { + backup_migrate_filters_invoke_all($op .'_settings_form_submit', $form, $form_state); +} + +/** + * Get the default settings for the filters. + */ +function backup_migrate_filters_settings_default($op) { + return backup_migrate_filters_invoke_all($op .'_settings_default'); +} + +/** + * Get the backup settings for all of the filters. + */ +function backup_migrate_filters_before_action_form($settings, $op) { + $out = array(); + $out += backup_migrate_filters_invoke_all('before_action_form', $op, $settings); + $out += backup_migrate_filters_invoke_all('before_' . $op .'_form', $settings); + return $out; +} + +/** + * Get the backup settings for all of the filters. + */ +function backup_migrate_filters_before_action_form_validate($settings, $op, $form, &$form_state) { + backup_migrate_filters_invoke_all('before_action_form_validate', $op, $settings, $form, $form_state); + backup_migrate_filters_invoke_all('before_' . $op .'_form_validate', $settings, $form, $form_state); +} + +/** + * Get the backup settings for all of the filters. + */ +function backup_migrate_filters_before_action_form_submit($settings, $op, $form, &$form_state) { + backup_migrate_filters_invoke_all('before_action_form_submit', $op, $settings, $form, $form_state); + backup_migrate_filters_invoke_all('before_' . $op .'_form_submit', $settings, $form, $form_state); +} + +/** + * Get the file types for all of the filters. + */ +function backup_migrate_filters_file_types() { + return backup_migrate_filters_invoke_all('file_types'); +} + +/** + * A base class for basing filters on. + */ +class backup_migrate_filter { + var $weight = 0; + var $op_weights = array(); + + /** + * Get the weight of the filter for the given op. + */ + function weight($op = NULL) { + if ($op && isset($this->op_weights[$op])) { + return $this->op_weights[$op]; + } + return $this->weight; + } + + /** + * Get the form for the settings for this filter. + */ + function backup_settings_default() { + return array(); + } + + /** + * Get the form for the settings for this filter. + */ + function backup_settings_form($settings) { + return array(); + } + + /** + * Get the form for the settings for this filter. + */ + function backup_settings_form_validate($form, &$form_state) { + } + + /** + * Submit the settings form. Any values returned will be saved. + */ + function backup_settings_form_submit($form, &$form_state) { + } + + /** + * Get the form for the settings for this filter. + */ + function restore_settings_default() { + return array(); + } + + /** + * Get the form for the settings for this filter. + */ + function restore_settings_form($settings) { + return array(); + } + + /** + * Get the form for the settings for this filter. + */ + function restore_settings_form_validate($form, &$form_state) { + } + + /** + * Submit the settings form. Any values returned will be saved. + */ + function restore_settings_form_submit($form, &$form_state) { + return $form_state['values']; + } + + /** + * Get a list of file types handled by this filter. + */ + function file_types() { + return array(); + } + + /** + * Declare any default destinations for this filter. + */ + function destinations() { + return array(); + } + + + /** + * This function is called on a backup file after the backup has been completed. + */ + function backup($file, $settings) { + return $file; + } + + /** + * This function is called immediately prior to backup. + */ + function pre_backup($file, $settings) { + + } + + /** + * This function is called immediately post backup. + */ + function post_backup($file, $settings) { + + } + + /** + * This function is called on a backup file before importing it. + */ + function restore($file, $settings) { + return $file; + } + + /** + * This function is called immediately prior to restore. + */ + function pre_restore($file, $settings) { + + } + + /** + * This function is called immediately post restore. + */ + function post_restore($file, $settings) { + + } +} + diff --git a/sites/all/modules/contrib/backup_migrate/includes/filters.statusnotify.inc b/sites/all/modules/contrib/backup_migrate/includes/filters.statusnotify.inc new file mode 100644 index 0000000..28387a2 --- /dev/null +++ b/sites/all/modules/contrib/backup_migrate/includes/filters.statusnotify.inc @@ -0,0 +1,114 @@ + FALSE, + 'notify_failure_enable' => FALSE, + 'notify_success_email' => variable_get('site_mail', ''), + 'notify_failure_email' => variable_get('site_mail', ''), + ); + } + + /** + * Get the form for the settings for this filter. + */ + function backup_settings_form($settings) { + $form = array(); + $form['advanced']['notify_success_enable'] = array( + "#type" => 'checkbox', + "#title" => t("Send an email if backup succeeds"), + "#default_value" => @$settings['notify_success_enable'], + ); + $form['advanced']['notify_success_email_wrapper'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'filters[notify_success_enable]' => TRUE, + ), + ); + $form['advanced']['notify_success_email_wrapper']['notify_success_email'] = array( + "#type" => "textfield", + "#title" => t("Email Address for Success Notices"), + "#default_value" => @$settings['notify_success_email'], + ); + $form['advanced']['notify_failure_enable'] = array( + "#type" => 'checkbox', + "#title" => t("Send an email if backup fails"), + "#default_value" => @$settings['notify_failure_enable'], + ); + $form['advanced']['notify_failure_email_wrapper'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'filters[notify_failure_enable]' => TRUE, + ), + ); + $form['advanced']['notify_failure_email_wrapper']['notify_failure_email'] = array( + "#type" => "textfield", + "#title" => t("Email Address for Failure Notices"), + "#default_value" => @$settings['notify_failure_email'], + ); + return $form; + } + + /** + * Send the success email. + */ + function backup_succeed($settings) { + if (@$settings->filters['notify_success_enable'] && $to = @$settings->filters['notify_success_email']) { + $messages = $this->get_messages(); + $subject = t('!site backup succeeded', array('!site' => variable_get('site_name', 'Drupal site'))); + if ($messages = $this->get_messages()) { + $body = t("The site backup has completed successfully with the following messages:\n!messages", array('!messages' => $messages)); + } + else { + $body = t("The site backup has completed successfully.\n"); + } + mail($settings->filters['notify_success_email'], $subject, $body); + } + } + + /** + * Send the failure email. + */ + function backup_fail($settings) { + if (@$settings->filters['notify_failure_enable'] && $to = @$settings->filters['notify_failure_email']) { + $messages = $this->get_messages(); + $subject = t('!site backup failed', array('!site' => variable_get('site_name', 'Drupal site'))); + if ($messages = $this->get_messages()) { + $body = t("The site backup has failed with the following messages:\n!messages", array('!messages' => $messages)); + } + else { + $body = t("The site backup has failed for an unknown reason."); + } + mail($settings->filters['notify_failure_email'], $subject, $body); + } + } + + /** + * Render the messages and errors for the email. + */ + function get_messages() { + $out = ""; + $messages = _backup_migrate_messages(); + foreach ($messages as $message) { + $out .= strip_tags(t($message['message'], $message['replace'])) . "\n"; + } + return $out; + } +} + diff --git a/sites/all/modules/contrib/backup_migrate/includes/filters.utils.inc b/sites/all/modules/contrib/backup_migrate/includes/filters.utils.inc new file mode 100644 index 0000000..861e09f --- /dev/null +++ b/sites/all/modules/contrib/backup_migrate/includes/filters.utils.inc @@ -0,0 +1,239 @@ + -1000, 'post_backup' => 1000); + var $saved_devel_query = NULL; + + /** + * Get the default backup settings for this filter. + */ + function backup_settings_default() { + return array( + 'utils_disable_query_log' => TRUE, + 'utils_site_offline' => FALSE, + 'utils_description' => '', + ); + } + + /** + * Get the default restore settings for this filter. + */ + function restore_settings_default() { + return array( + 'utils_disable_query_log' => TRUE, + 'utils_site_offline' => FALSE, + ); + } + + /** + * Get the form for the backup settings for this filter. + */ + function backup_settings_form($settings) { + $form = array(); + if (module_exists('devel') && variable_get('dev_query', 0)) { + $form['database']['utils_disable_query_log'] = array( + '#type' => 'checkbox', + '#title' => t('Disable query log'), + '#default_value' => !empty($settings['utils_disable_query_log']) ? $settings['utils_disable_query_log'] : NULL, + '#description' => t('Disable the devel module\'s query logging during the backup operation. It will be enabled again after backup is complete. This is very highly recommended.'), + ); + } + $form['advanced']['utils_site_offline'] = array( + '#type' => 'checkbox', + '#title' => t('Take site offline'), + '#default_value' => !empty($settings['utils_site_offline']) ? $settings['utils_site_offline'] : NULL, + '#description' => t('Take the site offline during backup and show a maintenance message. Site will be taken back online once the backup is complete.'), + ); + $form['advanced']['utils_site_offline_message_wrapper'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'filters[utils_site_offline]' => TRUE, + ), + ); + $form['advanced']['utils_site_offline_message_wrapper']['utils_site_offline_message'] = array( + '#type' => 'textarea', + '#title' => t('Site off-line message'), + '#default_value' => !empty($settings['utils_site_offline_message']) ? $settings['utils_site_offline_message'] : variable_get('site_offline_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))), + '#description' => t('Message to show visitors when the site is in off-line mode.') + ); + $form['advanced']['utils_description'] = array( + '#type' => 'textarea', + '#title' => t('Add a note'), + '#default_value' => !empty($settings['utils_description']) ? $settings['utils_description'] : NULL, + '#description' => t('Add a short note to the backup file.'), + ); + $form['advanced']['use_cli'] = array( + "#type" => "checkbox", + "#title" => t("Use cli commands"), + "#default_value" => !empty($settings['use_cli']), + "#description" => t("Use the command line tools (mysqldump, tar, gzip etc.) if available. This can be faster for large sites but will not work on all servers. EXPERIMENTAL"), + ); + $form['advanced']['ignore_errors'] = array( + "#type" => "checkbox", + "#title" => t("Ignore errors"), + "#default_value" => !empty($settings['ignore_errors']), + "#description" => t("Will attempt to complete backup even if certain recoverable errors occur. This may make the backup files invalid. Enable this if you have unreadable files that you want to ignore during backup."), + ); + + return $form; + } + + /** + * Get the form for the restore settings for this filter. + */ + function restore_settings_form($settings) { + $form = array(); + if (module_exists('devel') && variable_get('dev_query', 0)) { + $form['advanced']['utils_disable_query_log'] = array( + '#type' => 'checkbox', + '#title' => t('Disable query log'), + '#default_value' => @$settings['utils_disable_query_log'], + '#description' => t('Disable the devel module\'s query logging during the restore operation. It will be enabled again after restore is complete. This is very highly recommended.'), + ); + } + $form['advanced']['utils_site_offline'] = array( + '#type' => 'checkbox', + '#title' => t('Take site offline'), + '#default_value' => !empty($settings['utils_site_offline']) ? $settings['utils_site_offline'] : NULL, + '#description' => t('Take the site offline during restore and show a maintenance message. Site will be taken back online once the restore is complete.'), + ); + $form['advanced']['utils_site_offline_message_wrapper'] = array( + '#type' => 'backup_migrate_dependent', + '#dependencies' => array( + 'filters[utils_site_offline]' => TRUE, + ), + ); + $form['advanced']['utils_site_offline_message_wrapper']['utils_site_offline_message'] = array( + '#type' => 'textarea', + '#title' => t('Site off-line message'), + '#default_value' => !empty($settings['utils_site_offline_message']) ? $settings['utils_site_offline_message'] : variable_get('site_offline_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))), + '#description' => t('Message to show visitors when the site is in off-line mode.') + ); + $form['advanced']['use_cli'] = array( + "#type" => "checkbox", + "#title" => t("Use cli commands"), + "#default_value" => !empty($settings['use_cli']), + "#description" => t("Use the command line tools (mysqldump, tar, gzip etc.) if available. This can be faster for large sites but will not work on all servers. EXPERIMENTAL"), + ); + // $form['advanced']['ignore_errors'] = array( + // "#type" => "checkbox", + // "#title" => t("Ignore errors"), + // "#default_value" => !empty($settings['ignore_errors']), + // "#description" => t("Will attempt to complete restore even if certain recoverable errors occur. This may could corrupt your site."), + // ); + return $form; + } + + function pre_backup($file, $settings) { + $this->take_site_offline($settings); + $this->disable_devel_query($settings); + } + + function post_backup($file, $settings) { + $this->enable_devel_query($settings); + $this->take_site_online($settings); + if ($file) { + $this->add_file_info($file, $settings); + } + } + + function pre_restore($file, $settings) { + $this->disable_devel_query($settings); + $this->take_site_offline($settings); + } + + function post_restore($file, $settings) { + $this->enable_devel_query($settings); + $this->take_site_online($settings); + } + + /** + * Disable devel query logging if it's active and the user has chosen to do so. + */ + function disable_devel_query($settings) { + $this->saved_devel_query = variable_get('dev_query', 0); + if (module_exists('devel') && variable_get('dev_query', 0) && !empty($settings->filters['utils_disable_query_log'])) { + variable_set('dev_query', 0); + } + } + + /** + * Restore devel query to previous state. + */ + function enable_devel_query($settings) { + if (module_exists('devel')) { + variable_set('dev_query', $this->saved_devel_query); + } + } + + /** + * Add the backup metadata to the file. + */ + function add_file_info($file, $settings) { + $file->file_info['description'] = $settings->filters['utils_description']; + $file->file_info['datestamp'] = time(); + $file->file_info['generator'] = 'Backup and Migrate (http://drupal.org/project/backup_migrate)'; + $file->file_info['generatorversion'] = BACKUP_MIGRATE_VERSION; + $file->file_info['siteurl'] = url('', array('absolute' => TRUE)); + $file->file_info['sitename'] = variable_get('site_name', ''); + $file->file_info['drupalversion'] = VERSION; + $file->calculate_filesize(); + + $source = $settings->get('source'); + $file->file_info['bam_sourceid'] = $source->get('id'); + $file->file_info['bam_sourcetype'] = $source->get('subtype'); + $file->file_info['bam_sourcename'] = $source->get('name'); + + // Add any additional info that has been added to the settings by other plugins. + if (!empty($settings->file_info)) { + $file->file_info += $settings->file_info; + } + } + + /** + * Take the site offline if configured to do so. + */ + function take_site_offline($settings) { + // If the site is already offline then don't do anything. + if (!variable_get('maintenance_mode', 0)) { + // Save the current state of the site in case a restore overwrites it. + if (!empty($settings->filters['utils_site_offline'])) { + $this->saved_site_offline = TRUE; + $this->saved_site_offline_message = variable_get('maintenance_mode_message', NULL); + if (!empty($settings->filters['utils_site_offline_message'])) { + $this->saved_site_offline_message = variable_get('maintenance_mode_message', NULL); + variable_set('maintenance_mode_message', $settings->filters['utils_site_offline_message']); + } + variable_set('maintenance_mode', 1); + _backup_migrate_message('Site was taken offline.'); + } + } + } + + /** + * Take the site online again after backup or restore. + */ + function take_site_online($settings) { + // Take the site back off/online because the restored db may have changed that setting. + if (variable_get('maintenance_mode', 0) && !empty($this->saved_site_offline)) { + variable_set('maintenance_mode', 0); + if ($settings->filters['utils_site_offline']) { + if (!empty($this->saved_site_offline_message)) { + variable_set('maintenance_mode_message', $this->saved_site_offline_message); + } + _backup_migrate_message('Site was taken online.'); + } + } + } +} diff --git a/sites/all/modules/contrib/backup_migrate/includes/locations.inc b/sites/all/modules/contrib/backup_migrate/includes/locations.inc new file mode 100644 index 0000000..7cacd0f --- /dev/null +++ b/sites/all/modules/contrib/backup_migrate/includes/locations.inc @@ -0,0 +1,543 @@ + $location) { + if ($location->op($op)) { + $out[$key] = $location; + } + } + } + return $out; +} + +/** + * Get the location of the given id. + */ +function backup_migrate_get_location($id) { + $locations = backup_migrate_get_locations('all'); + return empty($locations[$id]) ? NULL : $locations[$id]; +} + + +/** + * A base class for creating locations. + */ +class backup_migrate_location extends backup_migrate_item { + var $db_table = "backup_migrate_destinations"; + var $type_name = "location"; + var $default_values = array('settings' => array()); + var $singular = 'location'; + var $plural = 'locations'; + var $title_plural = 'Locations'; + var $title_singular = 'Location'; + + var $subtype = ""; + var $supported_ops = array(); + + /** + * This function is not supposed to be called. It is just here to help the po extractor out. + */ + function strings() { + // Help the pot extractor find these strings. + t('location'); + t('locations'); + t('Location'); + t('Locations'); + } + + function ops() { + return $this->supported_ops; + } + + /** + * Does this location support the given operation. + */ + function op($op) { + $ops = (array)$this->ops(); + return in_array($op, $ops); + } + + /** + * Remove the given op from the support list. + */ + function remove_op($op) { + $key = array_search($op, $this->supported_ops); + if ($key !== FALSE) { + unset($this->supported_ops[$key]); + } + } + + function get_name() { + return @$this->name; + } + + function set_name($name) { + return $this->name = $name; + } + + function set_location($location) { + $this->location = $location; + } + + function get_location() { + return @$this->location; + } + + function get_display_location() { + return $this->get_location(); + } + + function settings($key = NULL) { + $out = $this->settings; + if ($key) { + $out = isset($out[$key]) ? $out[$key] : NULL; + } + return $out; + } + + /** + * Get the type name of this location for display to the user. + */ + function get_subtype_name() { + if ($type = $this->get('subtype')) { + $types = $this->location_types(); + return isset($types[$type]['type_name']) ? $types[$type]['type_name'] : $type; + } + } + + /** + * Get the edit form for the item. + */ + function edit_form() { + if (!empty($this->supported_ops)) { + $form = parent::edit_form(); + $form['subtype'] = array( + "#type" => "value", + "#default_value" => $this->get('subtype'), + ); + } + else { + $types = $this->location_types(); + $items = array(); + // If no (valid) node type has been provided, display a node type overview. + foreach ($types as $key => $type) { + if (@$type['can_create']) { + $type_url_str = str_replace('_', '-', $key); + $out = '' . t('There are no custom rulesets.') . '
'; + } + + $vars['blocks']['ctools_access_ruleset'] = array( + 'title' => t('Manage custom rulesets'), + 'link' => l(t('Go to list'), 'admin/structure/ctools-rulesets'), + 'content' => $content, + 'class' => 'dashboard-ruleset', + 'section' => 'right', + ); +} diff --git a/sites/all/modules/contrib/ctools/ctools_access_ruleset/plugins/access/ruleset.inc b/sites/all/modules/contrib/ctools/ctools_access_ruleset/plugins/access/ruleset.inc new file mode 100644 index 0000000..f8abea6 --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_access_ruleset/plugins/access/ruleset.inc @@ -0,0 +1,109 @@ + '', + 'description' => '', + 'callback' => 'ctools_ruleset_ctools_access_check', + 'settings form' => 'ctools_ruleset_ctools_access_settings', + 'summary' => 'ctools_ruleset_ctools_access_summary', + + // This access plugin actually just contains child plugins that are + // exportable, UI configured rulesets. + 'get child' => 'ctools_ruleset_ctools_access_get_child', + 'get children' => 'ctools_ruleset_ctools_access_get_children', +); + +/** + * Merge the main access plugin with a loaded ruleset to form a child plugin. + */ +function ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item) { + $plugin['name'] = $parent . ':' . $item->name; + $plugin['title'] = check_plain($item->admin_title); + $plugin['description'] = check_plain($item->admin_description); + + // TODO: Generalize this in CTools. + if (!empty($item->requiredcontexts)) { + $plugin['required context'] = array(); + foreach ($item->requiredcontexts as $context) { + $info = ctools_get_context($context['name']); + // TODO: allow an optional setting + $plugin['required context'][] = new ctools_context_required($context['identifier'], $info['context name']); + } + } + + // Store the loaded ruleset in the plugin. + $plugin['ruleset'] = $item; + return $plugin; +} + +/** + * Get a single child access plugin. + */ +function ctools_ruleset_ctools_access_get_child($plugin, $parent, $child) { + ctools_include('export'); + $item = ctools_export_crud_load('ctools_access_ruleset', $child); + if ($item) { + return ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item); + } +} + +/** + * Get all child access plugins. + */ +function ctools_ruleset_ctools_access_get_children($plugin, $parent) { + $plugins = array(); + ctools_include('export'); + $items = ctools_export_crud_load_all('ctools_access_ruleset'); + foreach ($items as $name => $item) { + $child = ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item); + $plugins[$child['name']] = $child; + } + + return $plugins; +} + +/** + * Settings form for the 'by ruleset' access plugin + */ +function ctools_ruleset_ctools_access_settings(&$form, &$form_state, $conf) { + if (!empty($form_state['plugin']['ruleset']->admin_description)) { + $form['markup'] = array( + '#markup' => '' . t('There are no custom content panes.') . '
'; + } + + $vars['blocks']['ctools_custom_content'] = array( + 'title' => t('Manage custom content'), + 'link' => l(t('Go to list'), 'admin/structure/ctools-content'), + 'content' => $content, + 'class' => 'dashboard-content', + 'section' => 'right', + ); +} diff --git a/sites/all/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc b/sites/all/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc new file mode 100644 index 0000000..467dc58 --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content.inc @@ -0,0 +1,20 @@ + 'ctools_custom_content', + 'access' => 'administer custom content', + + 'menu' => array( + 'menu item' => 'ctools-content', + 'menu title' => 'Custom content panes', + 'menu description' => 'Add, edit or delete custom content panes.', + ), + + 'title singular' => t('content pane'), + 'title singular proper' => t('Content pane'), + 'title plural' => t('content panes'), + 'title plural proper' => t('Content panes'), + + 'handler' => 'ctools_custom_content_ui', +); + diff --git a/sites/all/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php b/sites/all/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php new file mode 100644 index 0000000..56fe4b2 --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_custom_content/plugins/export_ui/ctools_custom_content_ui.class.php @@ -0,0 +1,129 @@ +settings['body'])) { + $form_state['item']->settings['format'] = $form_state['item']->settings['body']['format']; + $form_state['item']->settings['body'] = $form_state['item']->settings['body']['value']; + } + parent::edit_form($form, $form_state); + + $form['category'] = array( + '#type' => 'textfield', + '#title' => t('Category'), + '#description' => t('What category this content should appear in. If left blank the category will be "Miscellaneous".'), + '#default_value' => $form_state['item']->category, + ); + + $form['title'] = array( + '#type' => 'textfield', + '#default_value' => $form_state['item']->settings['title'], + '#title' => t('Title'), + ); + + $form['body'] = array( + '#type' => 'text_format', + '#title' => t('Body'), + '#default_value' => $form_state['item']->settings['body'], + '#format' => $form_state['item']->settings['format'], + ); + + $form['substitute'] = array( + '#type' => 'checkbox', + '#title' => t('Use context keywords'), + '#description' => t('If checked, context keywords will be substituted in this content.'), + '#default_value' => !empty($form_state['item']->settings['substitute']), + ); + } + + function edit_form_submit(&$form, &$form_state) { + parent::edit_form_submit($form, $form_state); + + // Since items in our settings are not in the schema, we have to do these manually: + $form_state['item']->settings['title'] = $form_state['values']['title']; + $form_state['item']->settings['body'] = $form_state['values']['body']['value']; + $form_state['item']->settings['format'] = $form_state['values']['body']['format']; + $form_state['item']->settings['substitute'] = $form_state['values']['substitute']; + } + + function list_form(&$form, &$form_state) { + parent::list_form($form, $form_state); + + $options = array('all' => t('- All -')); + foreach ($this->items as $item) { + $options[$item->category] = $item->category; + } + + $form['top row']['category'] = array( + '#type' => 'select', + '#title' => t('Category'), + '#options' => $options, + '#default_value' => 'all', + '#weight' => -10, + ); + } + + function list_filter($form_state, $item) { + if ($form_state['values']['category'] != 'all' && $form_state['values']['category'] != $item->category) { + return TRUE; + } + + return parent::list_filter($form_state, $item); + } + + function list_sort_options() { + return array( + 'disabled' => t('Enabled, title'), + 'title' => t('Title'), + 'name' => t('Name'), + 'category' => t('Category'), + 'storage' => t('Storage'), + ); + } + + function list_build_row($item, &$form_state, $operations) { + // Set up sorting + switch ($form_state['values']['order']) { + case 'disabled': + $this->sorts[$item->name] = empty($item->disabled) . $item->admin_title; + break; + case 'title': + $this->sorts[$item->name] = $item->admin_title; + break; + case 'name': + $this->sorts[$item->name] = $item->name; + break; + case 'category': + $this->sorts[$item->name] = $item->category; + break; + case 'storage': + $this->sorts[$item->name] = $item->type . $item->admin_title; + break; + } + + $ops = theme('links__ctools_dropbutton', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline')))); + + $this->rows[$item->name] = array( + 'data' => array( + array('data' => check_plain($item->name), 'class' => array('ctools-export-ui-name')), + array('data' => check_plain($item->admin_title), 'class' => array('ctools-export-ui-title')), + array('data' => check_plain($item->category), 'class' => array('ctools-export-ui-category')), + array('data' => $ops, 'class' => array('ctools-export-ui-operations')), + ), + 'title' => check_plain($item->admin_description), + 'class' => array(!empty($item->disabled) ? 'ctools-export-ui-disabled' : 'ctools-export-ui-enabled'), + ); + } + + function list_table_header() { + return array( + array('data' => t('Name'), 'class' => array('ctools-export-ui-name')), + array('data' => t('Title'), 'class' => array('ctools-export-ui-title')), + array('data' => t('Category'), 'class' => array('ctools-export-ui-category')), + array('data' => t('Operations'), 'class' => array('ctools-export-ui-operations')), + ); + } + +} diff --git a/sites/all/modules/contrib/ctools/ctools_plugin_example/README.txt b/sites/all/modules/contrib/ctools/ctools_plugin_example/README.txt new file mode 100644 index 0000000..42edcdc --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_plugin_example/README.txt @@ -0,0 +1,14 @@ + +The CTools Plugin Example is an example for developers of how to CTools +access, argument, content type, context, and relationship plugins. + +There are a number of ways to profit from this: + +1. The code itself intends to be as simple and self-explanatory as possible. + Nothing fancy is attempted: It's just trying to use the plugin API to show + how it can be used. + +2. There is a sample panel. You can access it at /ctools_plugin_example/xxxx + to see how it works. + +3. There is Advanced Help at admin/advanced_help/ctools_plugin_example. \ No newline at end of file diff --git a/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info b/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info new file mode 100644 index 0000000..d378641 --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.info @@ -0,0 +1,16 @@ +name = Chaos Tools (CTools) Plugin Example +description = Shows how an external module can provide ctools plugins (for Panels, etc.). +package = Chaos tool suite +version = CTOOLS_MODULE_VERSION +dependencies[] = ctools +dependencies[] = panels +dependencies[] = page_manager +dependencies[] = advanced_help +core = 7.x + +; Information added by Drupal.org packaging script on 2015-08-19 +version = "7.x-1.9" +core = "7.x" +project = "ctools" +datestamp = "1440020680" + diff --git a/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.module b/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.module new file mode 100644 index 0000000..01d5338 --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.module @@ -0,0 +1,94 @@ + 'CTools plugin example', + 'description' => t("Demonstration code, advanced help, and a demo panel to show how to build ctools plugins."), + 'page callback' => 'ctools_plugin_example_explanation_page', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_NORMAL_ITEM, + ); + + return $items; +} + +/** + * Implements hook_ctools_plugin_directory(). + * + * It simply tells panels where to find the .inc files that define various + * args, contexts, content_types. In this case the subdirectories of + * ctools_plugin_example/panels are used. + */ +function ctools_plugin_example_ctools_plugin_directory($module, $plugin) { + if ($module == 'ctools' && !empty($plugin)) { + return "plugins/$plugin"; + } +} + +/** + * Implement hook_ctools_plugin_api(). + * + * If you do this, CTools will pick up default panels pages in + *' . t("The CTools Plugin Example is simply a developer's demo of how to create plugins for CTools. It provides no useful functionality for an ordinary user.") . '
'; + + $content .= '' . t( + 'There is a demo panel demonstrating much of the functionality provided at + CTools demo panel, and you can find documentation on the examples at + !ctools_plugin_example_help. + CTools itself provides documentation at !ctools_help. Mostly, though, the code itself is intended to be the teacher. + You can find it in %path.', + array( + '@demo_url' => url('ctools_plugin_example/xxxxx'), + '!ctools_plugin_example_help' => theme('advanced_help_topic', array('module' => 'ctools_plugin_example', 'topic' => 'Chaos-Tools--CTools--Plugin-Examples', 'type' => 'title')), + '!ctools_help' => theme('advanced_help_topic', array('module' => 'ctools', 'topic' => 'plugins', 'type' => 'title')), + '%path' => drupal_get_path('module', 'ctools_plugin_example'), + )) . '
'; + + return $content; +} diff --git a/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc b/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc new file mode 100644 index 0000000..10a7619 --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_plugin_example/ctools_plugin_example.pages_default.inc @@ -0,0 +1,451 @@ +.pages_default.inc + * With this naming, no additional code needs to be provided. CTools will just find the file. + * The name of the hook isWe can use access plugins to determine access to a page or visibility of the panes in a page. Basically, we just determine access based on configuration settings and the various contexts that are available to us.
+The arbitrary example in plugins/access/arg_length.inc determines access based on the length of the simplecontext argument. You can configure whether access should be granted if the simplecontext argument is greater or less than some number.
+Contexts are fundamental to CTools, and they almost always start with an argument to a panels page, so we'll start there too.
+We first need to process an argument.
+We're going to work with a "Simplecontext" context type and argument, and then with a content type that displays it. So we'll start by with the Simplecontext argument plugin in plugins/arguments/simplecontext_arg.inc.
+Note that the name of the file (simplecontext_arg.inc) is built from the machine name of our plugin (simplecontext_arg). And note also that the primary function that we use to provide our argument (ctools_plugin_example_simplecontext_arg_ctools_arguments()) is also built from the machine name. This magic is most of the naming magic that you have to know.
+You can browse plugins/arguments/simplecontext_arg.inc and see the little that it does.
+This demonstration module is intended for developers to look at and play with. CTools plugins are not terribly difficult to do, but it can be hard to sort through the various arguments and required functions. The idea here is that you should have a starting point for most anything you want to do. Just work through the example, and then start experimenting with changing it.
+There are two parts to this demo:
+First, there is a sample panel provided that uses all the various plugins. It's at ctools_example/12345. You can edit the panel and configure all the panes on it.
+Second, the code is there for you to experiment with and change as you see fit. Sometimes starting with simple code and working with it can take you places that it's hard to go when you're looking at more complex examples.
+Now we get to the heart of the matter: Building a content type plugin. A content type plugin uses the contexts available to it to display something. plugins/content_types/simplecontext_content_type.inc does this work for us.
+Note that our content type also has an edit form which can be used to configure its behavior. This settings form is accessed through the panels interface, and it's up to you what the settings mean to the code and the generation of content in the display rendering.
+Now that we have a plugin for a simplecontext argument, we can create a plugin for a simplecontext context.
+Normally, a context would take an argument which is a key like a node ID (nid) and retrieve a more complex object from a database or whatever. In our example, we'll artificially transform the argument into an arbitrary "context" data object.
+plugins/contexts/simplecontext.inc implements our context.
+Note that there are actually two ways to create a context. The normal one, which we've been referring to, is to create a context from an argument. However, it is also possible to configure a context in a panel using the panels interface. This is quite inflexible, but might be useful for configuring single page. However, it means that we have a settings form for exactly that purpose. Our context would have to know how to create itself from a settings form as well as from an argument. Simplecontext can do that.
+A context plugin can also provide keywords that expose parts of its context using keywords like masterkeyword:dataitem. The node plugin for ctools has node:nid and node:title, for example. The simplecontext plugin here provides the simplest of keywords.
+ +Your module must provide a few things so that your plugins can be found.
+First, you need to implement hook_ctools_plugin_directory(). Here we're telling CTools that our plugins will be found in the module's directory in the plugins/<plugintype> directory. Context plugins will be in ctools_plugin_example/plugins/contexts, Content-type plugins will be in ctools_plugin_example/plugins/content_types.
+<?php
function ctools_plugin_example_ctools_plugin_directory($module, $plugin) {
if ($module == 'ctools' && !empty($plugin)) {
return "plugins/$plugin";
}
}
?>Second, if you module wants to provide default panels pages, you can implement hook_ctools_plugin_api(). CTools will then pick up your panels pages in the file named <modulename>.pages_default.inc.
+<?php
function ctools_plugin_example_ctools_plugin_api($module, $api) {
if ($module == 'panels_mini' && $api == 'panels_default') {
return array('version' => 1);
}
if ($module == 'page_manager' && $api == 'pages_default') {
return array('version' => 1);
}
}
?>Often a single data type can lead us to other data types. For example, a node has a user (the author) and the user has data associated with it.
+A relationship plugin allows this kind of data to be accessed.
+An example relationship plugin is provided in plugins/relationships/relcontext_from_simplecontext.inc. It looks at a simplecontext (which we got from an argument) and builds an (artificial) "relcontext" from that.
+!PQ}6}aR2}S07*qoM6N<$ Ef;?gI>i_@% literal 0 HcmV?d00001 diff --git a/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc new file mode 100644 index 0000000..3c02ab8 --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/no_context_content_type.inc @@ -0,0 +1,116 @@ + t('CTools example no context content type'), + 'description' => t('No context content type - requires and uses no context.'), + + // 'single' => TRUE means has no subtypes. + 'single' => TRUE, + // Constructor. + 'content_types' => array('no_context_content_type'), + // Name of a function which will render the block. + 'render callback' => 'no_context_content_type_render', + // The default context. + 'defaults' => array(), + + // This explicitly declares the config form. Without this line, the func would be + // ctools_plugin_example_no_context_content_type_edit_form. + 'edit form' => 'no_context_content_type_edit_form', + + // Icon goes in the directory with the content type. + 'icon' => 'icon_example.png', + 'category' => array(t('CTools Examples'), -9), + + // this example does not provide 'admin info', which would populate the + // panels builder page preview. +); + +/** + * Run-time rendering of the body of the block. + * + * @param $subtype + * @param $conf + * Configuration as done at admin time. + * @param $args + * @param $context + * Context - in this case we don't have any. + * + * @return + * An object with at least title and content members. + */ +function no_context_content_type_render($subtype, $conf, $args, $context) { + $block = new stdClass(); + + $ctools_help = theme('advanced_help_topic', array('module' => 'ctools', 'topic' => 'plugins', 'type' => 'title')); + $ctools_plugin_example_help = theme('advanced_help_topic', array('module' => 'ctools_plugin_example', 'topic' => 'Chaos-Tools--CTools--Plugin-Examples', 'type' => 'title')); + + // The title actually used in rendering + $block->title = check_plain("No-context content type"); + $block->content = t(" + Welcome to the CTools Plugin Example demonstration content type. + + This block is a content type which requires no context at all. It's like a custom block, + but not even that sophisticated. + + For more information on the example plugins, please see the advanced help for + + {$ctools_help} and {$ctools_plugin_example_help} ++ "); + if (!empty($conf)) { + $block->content .= 'The only information that can be displayed in this block comes from the code and its settings form:'; + $block->content .= '' . var_export($conf, TRUE) . ''; + } + + return $block; + +} + +/** + * 'Edit form' callback for the content type. + * This example just returns a form; validation and submission are standard drupal + * Note that if we had not provided an entry for this in hook_content_types, + * this could have had the default name + * ctools_plugin_example_no_context_content_type_edit_form. + * + */ +function no_context_content_type_edit_form($form, &$form_state) { + $conf = $form_state['conf']; + $form['item1'] = array( + '#type' => 'textfield', + '#title' => t('Item1'), + '#size' => 50, + '#description' => t('The setting for item 1.'), + '#default_value' => !empty($conf['item1']) ? $conf['item1'] : '', + '#prefix' => '', + '#suffix' => '', + ); + $form['item2'] = array( + '#type' => 'textfield', + '#title' => t('Item2'), + '#size' => 50, + '#description' => t('The setting for item 2'), + '#default_value' => !empty($conf['item2']) ? $conf['item2'] : '', + '#prefix' => '', + '#suffix' => '', + ); + return $form; +} + +function no_context_content_type_edit_form_submit($form, &$form_state) { + foreach (array('item1', 'item2') as $key) { + $form_state['conf'][$key] = $form_state['values'][$key]; + } +} diff --git a/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc new file mode 100644 index 0000000..bf54dce --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/relcontext_content_type.inc @@ -0,0 +1,103 @@ + t('CTools example relcontext content type'), + 'admin info' => 'ctools_plugin_example_relcontext_content_type_admin_info', + 'content_types' => 'relcontext_content_type', + 'single' => TRUE, + 'render callback' => 'relcontext_content_type_render', + // Icon goes in the directory with the content type. Here, in plugins/content_types. + 'icon' => 'icon_example.png', + 'description' => t('Relcontext content type - works with relcontext context.'), + 'required context' => new ctools_context_required(t('Relcontext'), 'relcontext'), + 'category' => array(t('CTools Examples'), -9), + 'edit form' => 'relcontext_edit_form', + + // this example does not provide 'admin info', which would populate the + // panels builder page preview. + +); + +/** + * Run-time rendering of the body of the block. + * + * @param $subtype + * @param $conf + * Configuration as done at admin time + * @param $args + * @param $context + * Context - in this case we don't have any + * + * @return + * An object with at least title and content members + */ +function relcontext_content_type_render($subtype, $conf, $args, $context) { + $data = $context->data; + $block = new stdClass(); + + // Don't forget to check this data if it's untrusted. + // The title actually used in rendering. + $block->title = "Relcontext content type"; + $block->content = t(" + This is a block of data created by the Relcontent content type. + Data in the block may be assembled from static text (like this) or from the + content type settings form (\$conf) for the content type, or from the context + that is passed in.
+ In our case, the configuration form (\$conf) has just one field, 'config_item_1; + and it's configured with: + "); + if (!empty($conf)) { + $block->content .= '' . var_export($conf['config_item_1'], TRUE) . ''; + } + if (!empty($context)) { + $block->content .= '
The args ($args) were' . + var_export($args, TRUE) . ''; + } + $block->content .= '
And the relcontext context ($context->data->description) + (which was created from a + simplecontext context) was' . + print_r($context->data->description, TRUE) . ''; + return $block; +} + +/** + * 'Edit' callback for the content type. + * This example just returns a form. + * + */ +function relcontext_edit_form($form, &$form_state) { + $conf = $form_state['conf']; + + $form['config_item_1'] = array( + '#type' => 'textfield', + '#title' => t('Config Item 1 (relcontext)'), + '#size' => 50, + '#description' => t('Setting for relcontext.'), + '#default_value' => !empty($conf['config_item_1']) ? $conf['config_item_1'] : '', + '#prefix' => '', + '#suffix' => '', + ); + return $form; +} + +function relcontext_edit_form_submit($form, &$form_state) { + foreach (element_children($form) as $key) { + if (!empty($form_state['values'][$key])) { + $form_state['conf'][$key] = $form_state['values'][$key]; + } + } +} diff --git a/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc new file mode 100644 index 0000000..a308683 --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/content_types/simplecontext_content_type.inc @@ -0,0 +1,129 @@ + t('Simplecontext content type'), + 'content_types' => 'simplecontext_content_type', + // 'single' means not to be subtyped. + 'single' => TRUE, + // Name of a function which will render the block. + 'render callback' => 'simplecontext_content_type_render', + + // Icon goes in the directory with the content type. + 'icon' => 'icon_example.png', + 'description' => t('Simplecontext content type - works with a simplecontext context.'), + 'required context' => new ctools_context_required(t('Simplecontext'), 'simplecontext'), + 'edit form' => 'simplecontext_content_type_edit_form', + 'admin title' => 'ctools_plugin_example_simplecontext_content_type_admin_title', + + // presents a block which is used in the preview of the data. + // Pn Panels this is the preview pane shown on the panels building page. + 'admin info' => 'ctools_plugin_example_simplecontext_content_type_admin_info', + 'category' => array(t('CTools Examples'), -9), +); + +function ctools_plugin_example_simplecontext_content_type_admin_title($subtype, $conf, $context = NULL) { + $output = t('Simplecontext'); + if ($conf['override_title'] && !empty($conf['override_title_text'])) { + $output = filter_xss_admin($conf['override_title_text']); + } + return $output; +} + +/** + * Callback to provide administrative info (the preview in panels when building + * a panel). + * + * In this case we'll render the content with a dummy argument and + * a dummy context. + */ +function ctools_plugin_example_simplecontext_content_type_admin_info($subtype, $conf, $context = NULL) { + $context = new stdClass(); + $context->data = new stdClass(); + $context->data->description = t("no real context"); + $block = simplecontext_content_type_render($subtype, $conf, array("Example"), $context); + return $block; +} + +/** + * Run-time rendering of the body of the block (content type) + * + * @param $subtype + * @param $conf + * Configuration as done at admin time + * @param $args + * @param $context + * Context - in this case we don't have any + * + * @return + * An object with at least title and content members + */ +function simplecontext_content_type_render($subtype, $conf, $args, $context) { + $data = $context->data; + $block = new stdClass(); + + // Don't forget to check this data if it's untrusted. + // The title actually used in rendering. + $block->title = "Simplecontext content type"; + $block->content = t(" + This is a block of data created by the Simplecontext content type. + Data in the block may be assembled from static text (like this) or from the + content type settings form (\$conf) for the content type, or from the context + that is passed in.
+ In our case, the configuration form (\$conf) has just one field, 'config_item_1; + and it's configured with: + "); + if (!empty($conf)) { + $block->content .= '' . print_r(filter_xss_admin($conf['config_item_1']), TRUE) . ''; + } + if (!empty($context)) { + $block->content .= '
The args ($args) were' . + var_export($args, TRUE) . ''; + } + $block->content .= '
And the simplecontext context ($context->data->description) was' . + print_r($context->data->description, TRUE) . ''; + return $block; +} + +/** + * 'Edit' callback for the content type. + * This example just returns a form. + * + */ +function simplecontext_content_type_edit_form($form, &$form_state) { + $conf = $form_state['conf']; + $form['config_item_1'] = array( + '#type' => 'textfield', + '#title' => t('Config Item 1 for simplecontext content type'), + '#size' => 50, + '#description' => t('The stuff for item 1.'), + '#default_value' => !empty($conf['config_item_1']) ? $conf['config_item_1'] : '', + '#prefix' => '', + '#suffix' => '', + ); + + return $form; +} + +function simplecontext_content_type_edit_form_submit($form, &$form_state) { + foreach (element_children($form) as $key) { + if (!empty($form_state['values'][$key])) { + $form_state['conf'][$key] = $form_state['values'][$key]; + } + } +} diff --git a/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc new file mode 100644 index 0000000..0c7ef11 --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/relcontext.inc @@ -0,0 +1,83 @@ + t("Relcontext"), + 'description' => t('A relcontext object.'), + // Function to create the relcontext. + 'context' => 'ctools_plugin_example_context_create_relcontext', + // Function that does the settings. + 'settings form' => 'relcontext_settings_form', + 'keyword' => 'relcontext', + 'context name' => 'relcontext', +); + +/** + * Create a context, either from manual configuration (form) or from an argument on the URL. + * + * @param $empty + * If true, just return an empty context. + * @param $data + * If from settings form, an array as from a form. If from argument, a string. + * @param $conf + * TRUE if the $data is coming from admin configuration, FALSE if it's from a URL arg. + * + * @return + * a Context object. + */ +function ctools_plugin_example_context_create_relcontext($empty, $data = NULL, $conf = FALSE) { + $context = new ctools_context('relcontext'); + $context->plugin = 'relcontext'; + if ($empty) { + return $context; + } + if ($conf) { + if (!empty($data)) { + $context->data = new stdClass(); + // For this simple item we'll just create our data by stripping non-alpha and + // adding 'sample_relcontext_setting' to it. + $context->data->description = 'relcontext_from__' . preg_replace('/[^a-z]/i', '', $data['sample_relcontext_setting']); + $context->data->description .= '_from_configuration_sample_simplecontext_setting'; + $context->title = t("Relcontext context from simplecontext"); + return $context; + } + } + else { + // $data is coming from an arg - it's just a string. + // This is used for keyword. + $context->title = "relcontext_" . $data->data->description; + $context->argument = $data->argument; + // Make up a bogus context. + $context->data = new stdClass(); + // For this simple item we'll just create our data by stripping non-alpha and + // prepend 'relcontext_' and adding '_created_from_from_simplecontext' to it. + $context->data->description = 'relcontext_' . preg_replace('/[^a-z]/i', '', $data->data->description); + $context->data->description .= '_created_from_simplecontext'; + return $context; + } +} + +function relcontext_settings_form($conf, $external = FALSE) { + $form = array(); + + $form['sample_relcontext_setting'] = array( + '#type' => 'textfield', + '#title' => t('Relcontext setting'), + '#size' => 50, + '#description' => t('Just an example setting.'), + '#default_value' => !empty($conf['sample_relcontext_setting']) ? $conf['sample_relcontext_setting'] : '', + '#prefix' => '', + '#suffix' => '', + ); + return $form; +} + diff --git a/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc new file mode 100644 index 0000000..e19a842 --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/contexts/simplecontext.inc @@ -0,0 +1,134 @@ + t("Simplecontext"), + 'description' => t('A single "simplecontext" context, or data element.'), + 'context' => 'ctools_plugin_example_context_create_simplecontext', // func to create context + 'context name' => 'simplecontext', + 'settings form' => 'simplecontext_settings_form', + 'keyword' => 'simplecontext', + + // Provides a list of items which are exposed as keywords. + 'convert list' => 'simplecontext_convert_list', + // Convert keywords into data. + 'convert' => 'simplecontext_convert', + + 'placeholder form' => array( + '#type' => 'textfield', + '#description' => t('Enter some data to represent this "simplecontext".'), + ), +); + +/** + * Create a context, either from manual configuration or from an argument on the URL. + * + * @param $empty + * If true, just return an empty context. + * @param $data + * If from settings form, an array as from a form. If from argument, a string. + * @param $conf + * TRUE if the $data is coming from admin configuration, FALSE if it's from a URL arg. + * + * @return + * a Context object/ + */ +function ctools_plugin_example_context_create_simplecontext($empty, $data = NULL, $conf = FALSE) { + $context = new ctools_context('simplecontext'); + $context->plugin = 'simplecontext'; + + if ($empty) { + return $context; + } + + if ($conf) { + if (!empty($data)) { + $context->data = new stdClass(); + // For this simple item we'll just create our data by stripping non-alpha and + // adding '_from_configuration_item_1' to it. + $context->data->item1 = t("Item1"); + $context->data->item2 = t("Item2"); + $context->data->description = preg_replace('/[^a-z]/i', '', $data['sample_simplecontext_setting']); + $context->data->description .= '_from_configuration_sample_simplecontext_setting'; + $context->title = t("Simplecontext context from config"); + return $context; + } + } + else { + // $data is coming from an arg - it's just a string. + // This is used for keyword. + $context->title = $data; + $context->argument = $data; + // Make up a bogus context + $context->data = new stdClass(); + $context->data->item1 = t("Item1"); + $context->data->item2 = t("Item2"); + + // For this simple item we'll just create our data by stripping non-alpha and + // adding '_from_simplecontext_argument' to it. + $context->data->description = preg_replace('/[^a-z]/i', '', $data); + $context->data->description .= '_from_simplecontext_argument'; + $context->arg_length = strlen($context->argument); + return $context; + } +} + +function simplecontext_settings_form($conf, $external = FALSE) { + if (empty($conf)) { + $conf = array( + 'sample_simplecontext_setting' => 'default simplecontext setting', + ); + } + $form = array(); + $form['sample_simplecontext_setting'] = array( + '#type' => 'textfield', + '#title' => t('Setting for simplecontext'), + '#size' => 50, + '#description' => t('An example setting that could be used to configure a context'), + '#default_value' => $conf['sample_simplecontext_setting'], + '#prefix' => '', + '#suffix' => '', + ); + return $form; +} + + + +/** + * Provide a list of sub-keywords. + * + * This is used to provide keywords from the context for use in a content type, + * pane, etc. + */ +function simplecontext_convert_list() { + return array( + 'item1' => t('Item1'), + 'item2' => t('Item2'), + 'description' => t('Description'), + ); +} + +/** + * Convert a context into a string to be used as a keyword by content types, etc. + */ +function simplecontext_convert($context, $type) { + switch ($type) { + case 'item1': + return $context->data->item1; + case 'item2': + return $context->data->item2; + case 'description': + return $context->data->description; + } +} + diff --git a/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/panels.pages.inc b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/panels.pages.inc new file mode 100644 index 0000000..d3022af --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/panels.pages.inc @@ -0,0 +1,214 @@ +pid = 'new'; + $page->did = 'new'; + $page->name = 'ctools_plugin_example_demo_panel'; + $page->title = 'Panels Plugin Example Demo Panel'; + $page->access = array(); + $page->path = 'demo_panel'; + $page->load_flags = 1; + $page->css_id = ''; + $page->arguments = array( + 0 => + array( + 'name' => 'simplecontext_arg', + 'id' => 1, + 'default' => '404', + 'title' => '', + 'identifier' => 'Simplecontext arg', + 'keyword' => 'simplecontext', + ), + ); + $page->relationships = array( + 0 => + array( + 'context' => 'argument_simplecontext_arg_1', + 'name' => 'relcontext_from_simplecontext', + 'id' => 1, + 'identifier' => 'Relcontext from Simplecontext', + 'keyword' => 'relcontext', + ), + ); + $page->no_blocks = '0'; + $page->switcher_options = array(); + $page->menu = '0'; + $page->contexts = array(); + $display = new ctools_display(); + $display->did = 'new'; + $display->layout = 'threecol_33_34_33_stacked'; + $display->layout_settings = array(); + $display->panel_settings = array(); + $display->content = array(); + $display->panels = array(); + $pane = new stdClass(); + $pane->pid = 'new-1'; + $pane->panel = 'left'; + $pane->type = 'custom'; + $pane->shown = '1'; + $pane->subtype = 'custom'; + $pane->access = array(); + $pane->configuration = array( + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => '"No Context Item"', + 'body' => 'The "no context item" content type is here to demonstrate that you can create a content_type that does not require a context. This is probably the same as just creating a custom php block on the fly, and might serve the same purpose.', + 'format' => '1', + ); + $pane->cache = array(); + $display->content['new-1'] = $pane; + $display->panels['left'][0] = 'new-1'; + $pane = new stdClass(); + $pane->pid = 'new-2'; + $pane->panel = 'left'; + $pane->type = 'no_context_item'; + $pane->shown = '1'; + $pane->subtype = 'description'; + $pane->access = array(); + $pane->configuration = array( + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'item1' => 'one', + 'item2' => 'two', + 'item3' => 'three', + ); + $pane->cache = array(); + $display->content['new-2'] = $pane; + $display->panels['left'][1] = 'new-2'; + $pane = new stdClass(); + $pane->pid = 'new-3'; + $pane->panel = 'middle'; + $pane->type = 'custom'; + $pane->shown = '1'; + $pane->subtype = 'custom'; + $pane->access = array(); + $pane->configuration = array( + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => 'Simplecontext', + 'body' => 'The "Simplecontext" content and content type demonstrate a very basic context and how to display it. + + Simplecontext includes configuration, so it can get info from the config. It can also get its information to run from a simplecontext context, generated either from an arg to the panels page or via explicitly adding a context to the page.', + 'format' => '1', + ); + $pane->cache = array(); + $display->content['new-3'] = $pane; + $display->panels['middle'][0] = 'new-3'; + $pane = new stdClass(); + $pane->pid = 'new-4'; + $pane->panel = 'middle'; + $pane->type = 'simplecontext_item'; + $pane->shown = '1'; + $pane->subtype = 'description'; + $pane->access = array( + 0 => '2', + 1 => '4', + ); + $pane->configuration = array( + 'context' => 'argument_simplecontext_arg_1', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'config_item_1' => 'simplecontext called from arg', + ); + $pane->cache = array(); + $display->content['new-4'] = $pane; + $display->panels['middle'][1] = 'new-4'; + $pane = new stdClass(); + $pane->pid = 'new-5'; + $pane->panel = 'right'; + $pane->type = 'custom'; + $pane->shown = '1'; + $pane->subtype = 'custom'; + $pane->access = array(); + $pane->configuration = array( + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => 'Relcontext', + 'body' => 'The relcontext content_type gets its data from a relcontext, which is an example of a relationship. This panel should be run with an argument like "/xxx", which allows the simplecontext to get its context, and then the relcontext is configured in this panel to get (create) its data from the simplecontext.', + 'format' => '1', + ); + $pane->cache = array(); + $display->content['new-5'] = $pane; + $display->panels['right'][0] = 'new-5'; + $pane = new stdClass(); + $pane->pid = 'new-6'; + $pane->panel = 'right'; + $pane->type = 'relcontext_item'; + $pane->shown = '1'; + $pane->subtype = 'description'; + $pane->access = array(); + $pane->configuration = array( + 'context' => 'relationship_relcontext_from_simplecontext_1', + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'config_item_1' => 'default1', + ); + $pane->cache = array(); + $display->content['new-6'] = $pane; + $display->panels['right'][1] = 'new-6'; + $pane = new stdClass(); + $pane->pid = 'new-7'; + $pane->panel = 'top'; + $pane->type = 'custom_php'; + $pane->shown = '1'; + $pane->subtype = 'custom_php'; + $pane->access = array(); + $pane->configuration = array( + 'style' => 'default', + 'override_title' => 0, + 'override_title_text' => '', + 'css_id' => '', + 'css_class' => '', + 'title' => '', + 'body' => '$arg = arg(1); + $arg0 = arg(0); + if (!$arg) { + $block->content = <<This page is intended to run with an arg and you don\'t have one. +
+ Without an arg, the page doesn\'t have any context. +
Please try something like "/$arg0/xxx" +END; + + $block->title = "This is intended to run with an argument"; + } else { + $block->content = "The arg for this page is \'$arg\'"; + }', + ); + $pane->cache = array(); + $display->content['new-7'] = $pane; + $display->panels['top'][0] = 'new-7'; + $page->display = $display; + $page->displays = array(); + $pages['ctools_plugin_example'] = $page; + + + return $pages; +} diff --git a/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc new file mode 100644 index 0000000..6224621 --- /dev/null +++ b/sites/all/modules/contrib/ctools/ctools_plugin_example/plugins/relationships/relcontext_from_simplecontext.inc @@ -0,0 +1,50 @@ + t("Relcontext from simplecontext"), + 'keyword' => 'relcontext', + 'description' => t('Adds a relcontext from existing simplecontext.'), + 'required context' => new ctools_context_required(t('Simplecontext'), 'simplecontext'), + 'context' => 'ctools_relcontext_from_simplecontext_context', + 'settings form' => 'ctools_relcontext_from_simplecontext_settings_form', +); + +/** + * Return a new context based on an existing context. + */ +function ctools_relcontext_from_simplecontext_context($context = NULL, $conf) { + // If unset it wants a generic, unfilled context, which is just NULL. + if (empty($context->data)) { + return ctools_context_create_empty('relcontext', NULL); + } + + // You should do error-checking here. + + // Create the new context from some element of the parent context. + // In this case, we'll pass in the whole context so it can be used to + // create the relcontext. + return ctools_context_create('relcontext', $context); +} + +/** + * Settings form for the relationship. + */ +function ctools_relcontext_from_simplecontext_settings_form($conf) { + // We won't configure it in this case. + return array(); +} + diff --git a/sites/all/modules/contrib/ctools/drush/ctools.drush.inc b/sites/all/modules/contrib/ctools/drush/ctools.drush.inc new file mode 100644 index 0000000..1862dbe --- /dev/null +++ b/sites/all/modules/contrib/ctools/drush/ctools.drush.inc @@ -0,0 +1,1017 @@ + array('ctex'), + 'callback' => 'ctools_drush_export', + 'description' => 'Export multiple CTools exportable objects directly to code.', + 'arguments' => array( + 'module' => 'Name of your module.', + ), + 'options' => array( + 'subdir' => 'The name of the sub directory to create the module in. Defaults to ctools_export which will be placed into sites/all/modules.', + 'remove' => 'Remove existing files before writing, except the .module file.', + 'filter' => 'Filter the list of exportables by status. Available options are enabled, disabled, overridden, database, code and all. Defaults to enabled.', + 'tables' => 'Comma separated list of exportable table names to filter by.', + ), + 'examples' => array( + 'drush ctex export_module' => 'Export CTools exportables to a module called "export_module".', + 'drush ctex export_module --subdir=exports' => 'Same as above, but into the sites/all/modules/exports directory.', + 'drush ctex export_module --subdir=exports --remove' => 'Same as above, but automatically removing all files, except for the .module file.', + 'drush ctex --filter="views_view"' => 'Filter export selection to the views_view table only.', + ), + ); + + $items['ctools-export-info'] = array( + 'aliases' => array('ctei'), + 'callback' => 'ctools_drush_export_info', + 'description' => 'Show available CTools exportable objects.', + 'arguments' => array(), + 'options' => array( + 'format' => 'Display exportables info in a different format such as print_r, json, export. The default is to show in a tabular format.', + 'tables-only' => 'Only show list of exportable types/table names and not available objects.', + 'filter' => 'Filter the list of exportables by status. Available options are enabled, disabled, overridden, database, and code.', + 'module' => $module_text, + ), + 'examples' => array( + 'drush ctools-export-info' => 'View export info on all exportables.', + 'drush ctools-export-info views_view variable' => 'View export info for views_view and variable exportable types only.', + 'drush ctei --filter=enabled' => 'Show all enabled exportables.', + 'drush ctei views_view --filter=disabled' => 'Show all enabled exportables.', + 'drush ctei views_view --module=node' => 'Show all exportables provided by/on behalf of the node module.', + ), + ); + + $items['ctools-export-view'] = array( + 'aliases' => array('ctev'), + 'callback' => 'ctools_drush_export_op_command', + 'description' => 'View CTools exportable object code output.', + 'arguments' => array( + 'table name' => 'Base table of the exportable you want to view.', + 'machine names' => 'Space separated list of exportables you want to view.', + ), + 'options' => array( + 'indent' => 'The string to use for indentation when dispalying the exportable export code. Defaults to \'\'.', + 'no-colour' => 'Remove any colour formatting from export string output. Ideal if you are sending the output of this command to a file.', + 'module' => $module_text, + 'all' => $all_text, + ), + 'examples' => array( + 'drush ctools-export-view views_view' => 'View all views exportable objects.', + 'drush ctools-export-view views_view archive' => 'View default views archive view.', + ), + ); + + $items['ctools-export-revert'] = array( + 'aliases' => array('cter'), + 'callback' => 'ctools_drush_export_op_command', + 'description' => 'Revert CTools exportables from changes overridden in the database.', + 'arguments' => array( + 'table name' => 'Base table of the exportable you want to revert.', + 'machine names' => 'Space separated list of exportables you want to revert.', + ), + 'options' => array( + 'module' => $module_text, + 'all' => $all_text, + ), + 'examples' => array( + 'drush ctools-export-revert views_view' => 'Revert all overridden views exportable objects.', + 'drush ctools-export-revert views_view archive' => 'Revert overridden default views archive view.', + 'drush ctools-export-revert --all' => 'Revert all exportables on the system.', + ), + ); + + $items['ctools-export-enable'] = array( + 'aliases' => array('ctee'), + 'callback' => 'ctools_drush_export_op_command', + 'description' => 'Enable CTools exportables.', + 'arguments' => array( + 'table name' => 'Base table of the exportable you want to enable.', + 'machine names' => 'Space separated list of exportables you want to enable.', + ), + 'options' => array( + 'module' => $module_text, + 'all' => $all_text, + ), + 'examples' => array( + 'drush ctools-export-enable views_view' => 'Enable all overridden views exportable objects.', + 'drush ctools-export-enable views_view archive' => 'Enable overridden default views archive view.', + ), + ); + + $items['ctools-export-disable'] = array( + 'aliases' => array('cted'), + 'callback' => 'ctools_drush_export_op_command', + 'description' => 'Disable CTools exportables.', + 'arguments' => array( + 'table name' => 'Base table of the exportable you want to disable.', + 'machine names' => 'Space separated list of exportables you want to disable.', + ), + 'options' => array( + 'module' => $module_text, + 'all' => $all_text, + ), + 'examples' => array( + 'drush ctools-export-disable views_view' => 'Disable all overridden views exportable objects.', + 'drush ctools-export-disable views_view archive' => 'Disable overridden default views archive view.', + ), + ); + + return $items; +} + +/** + * Implementation of hook_drush_help(). + */ +function ctools_drush_help($section) { + switch ($section) { + case 'meta:ctools:title': + return dt('CTools commands'); + case 'meta:entity:summary': + return dt('CTools drush commands.'); + } +} + +/** + * Drush callback: export + */ +function ctools_drush_export($module = 'foo') { + $error = FALSE; + if (preg_match('@[^a-z_]+@', $module)) { + $error = dt('The name of the module must contain only lowercase letters and underscores') . '.'; + drush_log($error, 'error'); + return; + } + + // Filter by tables. + $tables = _ctools_drush_explode_options('tables'); + + // Check status. + $filter = drush_get_option('filter', FALSE); + if (empty($filter)) { + drush_set_option('filter', 'enabled'); + } + + // Selection. + $options = array('all' => dt('Export everything'), 'select' => dt('Make selection')); + $selection = drush_choice($options, dt('Select to proceed')); + + if (!$selection) { + return; + } + + // Present the selection screens. + if ($selection == 'select') { + $selections = _ctools_drush_selection_screen($tables); + } + else { + $info = _ctools_drush_export_info($tables, TRUE); + $selections = $info['exportables']; + } + + // Subdirectory. + $dest_exists = FALSE; + $subdir = drush_get_option('subdir', 'ctools_export'); + $dest = 'sites/all/modules/' . $subdir . '/' . $module; + + // Overwriting files can be set with 'remove' argument. + $remove = drush_get_option('remove', FALSE); + + // Check if folder exists. + if (file_exists($dest)) { + $dest_exists = TRUE; + if ($remove) { + if (drush_confirm(dt('All files except for the .info and .module files in !module will be removed. You can choose later if you want to overwrite these files as well. Are you sure you want to proceed ?', array('!module' => $module)))) { + $remove = TRUE; + drush_log(dt('Files will be removed'), 'success'); + } + else { + drush_log(dt('Export aborted.'), 'success'); + return; + } + } + } + + // Remove files (except for the .module file) if the destination folder exists. + if ($remove && $dest_exists) { + _ctools_drush_file_delete($dest); + } + + // Create new dir if needed. + if (!$dest_exists) { + if (!file_exists('sites/all/modules/' . $subdir)) { + drush_mkdir('sites/all/modules/' . $subdir); + } + } + + // Create destination directory. + drush_mkdir($dest); + + // Load bulk export module. + module_load_include('module', 'bulk_export'); + + // Create options and call Bulk export function. + // We create an array, because maybe in the future we can pass in more + // options to the export function (pre-selected modules and/or exportables). + $options = array( + 'name' => $module, + 'selections' => $selections, + ); + $files = bulk_export_export(TRUE, $options); + + $alter = array( + 'module' => $module, + 'files' => $files, + ); + // Let other drush commands alter the files. + drush_command_invoke_all_ref('drush_ctex_files_alter', $alter); + $files = $alter['files']; + + // Start writing. + if (is_array($files)) { + foreach ($files as $base_file => $data) { + $filename = $dest . '/' . $base_file; + // Extra check for .module file. + if ($base_file == ($module . '.module' || $module . '.info') && file_exists($filename)) { + if (!drush_confirm(dt('Do you want to overwrite !module_file', array('!module_file' => $base_file)))) { + drush_log(dt('Writing of !filename skipped. This is the code that was supposed to be written:', array('!filename' => $filename)), 'success'); + drush_print('---------'); + drush_print(shellColours::getColouredOutput("\n$data", 'light_green')); + drush_print('---------'); + continue; + } + } + if (file_put_contents($filename, $data)) { + drush_log(dt('Succesfully written !filename', array('!filename' => $filename)), 'success'); + } + else { + drush_log(dt('Error writing !filename', array('!filename' => $filename)), 'error'); + } + } + } + else { + drush_log(dt('No files were found to be written.'), 'error'); + } +} + +/** + * Helper function to select the exportables. By default, all exportables + * will be selected, so it will be easier to deselect them. + * + * @param $tables + */ +function _ctools_drush_selection_screen(array $tables = array()) { + $selections = $build = array(); + $files = system_rebuild_module_data(); + + $selection_number = 0; + + $info = _ctools_drush_export_info($tables, TRUE); + $exportables = $info['exportables']; + $schemas = $info['schemas']; + + $export_tables = array(); + + foreach (array_keys($exportables) as $table) { + natcasesort($exportables[$table]); + $export_tables[$table] = $files[$schemas[$table]['module']]->info['name'] . ' (' . $table . ')'; + } + + foreach ($export_tables as $table => $table_title) { + if (!empty($exportables[$table])) { + $table_count = count($exportables[$table]); + $selection_number += $table_count; + foreach ($exportables[$table] as $key => $title) { + $build[$table]['title'] = $table_title; + $build[$table]['items'][$key] = $title; + $build[$table]['count'] = $table_count; + $selections[$table][$key] = $key; + } + } + } + + drush_print(dt('Number of exportables selected: !number', array('!number' => $selection_number))); + drush_print(dt('By default all exportables are selected. Select a table to deselect exportables. Select "cancel" to start writing the files.')); + + // Let's go into a loop. + $return = FALSE; + while (!$return) { + + // Present the tables choice. + $table_rows = array(); + foreach ($build as $table => $info) { + $table_rows[$table] = $info['title'] . ' (' . $info['count'] . ')'; + } + $table_choice = drush_choice($table_rows, dt('Select a table. Select cancel to start writing files.')); + + // Bail out. + if (!$table_choice) { + drush_log(dt('Selection mode done, starting to write the files.'), 'notice'); + $return = TRUE; + return $selections; + } + + // Present the exportables choice, using the drush_choice_multiple. + $max = count($build[$table_choice]['items']); + $exportable_rows = array(); + foreach ($build[$table_choice]['items'] as $key => $title) { + $exportable_rows[$key] = $title; + } + drush_print(dt('Exportables from !table', array('!table' => $build[$table_choice]['title']))); + $multi_select = drush_choice_multiple($exportable_rows, $selections[$table_choice], dt('Select exportables.'), '!value', '!value (selected)', 0, $max); + + // Update selections. + if (is_array($multi_select)) { + $build[$table_choice]['count'] = count($multi_select); + $selections[$table_choice] = array(); + foreach ($multi_select as $key) { + $selections[$table_choice][$key] = $key; + } + } + } +} + +/** + * Delete files in a directory, keeping the .module and .info files. + * + * @param $path + * Path to directory in which to remove files. + */ +function _ctools_drush_file_delete($path) { + if (is_dir($path)) { + $files = new DirectoryIterator($path); + foreach ($files as $fileInfo) { + if (!$fileInfo->isDot() && !in_array($fileInfo->getExtension(), array('module', 'info'))) { + unlink($fileInfo->getPathname()); + } + } + } +} + +/** + * Drush callback: Export info. + * + * @params $table_names + * Each argument will be taken as a CTools exportable table name. + */ +function ctools_drush_export_info() { + // Collect array of table names from args. + $table_names = func_get_args(); + + // Get format option to allow for alternative output. + $format = drush_get_option('format', FALSE); + $tables_only = drush_get_option('tables-only', FALSE); + $filter = drush_get_option('filter', FALSE); + $export_module = drush_get_option('module', FALSE); + + $load = (bool) $filter || $export_module; + + // Get info on these tables, or all tables if none specified. + $info = _ctools_drush_export_info($table_names, $load); + $schemas = $info['schemas']; + $exportables = $info['exportables']; + + if (empty($exportables)) { + drush_log(dt('There are no exportables available.'), 'warning'); + return; + } + + // Filter by export module. + if (is_string($export_module)) { + $exportables = _ctools_drush_export_module_filter($exportables, $export_module); + } + + if (empty($exportables)) { + drush_log(dt('There are no exportables matching this criteria.'), 'notice'); + return; + } + + $exportable_counts = _ctools_drush_count_exportables($exportables); + + // Only use array keys if --tables-only option is set. + if ($tables_only) { + $exportables = array_keys($exportables); + } + + // Use format from --format option if it's present, and send to drush_format. + if ($format) { + // Create array with all exportable info and counts in one. + $output = array( + 'exportables' => $exportables, + 'count' => $exportable_counts, + ); + drush_print(drush_format($output, NULL, $format)); + } + // Build a tabular output as default. + else { + $header = $tables_only ? array() : array(dt('Module'), dt('Base table'), dt('Exportables')); + $rows = array(); + foreach ($exportables as $table => $info) { + if (is_array($info)) { + $row = array( + $schemas[$table]['module'], + $table, + // Machine name is better for this? + shellColours::getColouredOutput(implode("\n", array_keys($info)), 'light_green') . "\n", + ); + $rows[] = $row; + } + else { + $rows[] = array($info); + } + } + if (!empty($rows)) { + drush_print("\n"); + array_unshift($rows, $header); + drush_print_table($rows, TRUE, array(20, 20)); + drush_print(dt('Total exportables found: !total', array('!total' => $exportable_counts['total']))); + foreach ($exportable_counts['exportables'] as $table_name => $count) { + drush_print(dt('!table_name (!count)', array('!table_name' => $table_name, '!count' => $count)), 2); + } + drush_print("\n"); + } + } +} +/** + * Drush callback: Acts as the hub for all op commands to keep + * all arg handling etc in one place. + */ +function ctools_drush_export_op_command() { + // Get all info for the current drush command. + $command = drush_get_command(); + $op = ''; + + switch ($command['command']) { + case 'ctools-export-view': + $op = 'view'; + break; + case 'ctools-export-revert': + // Revert is same as deleting. As any objects in the db are deleted. + $op = 'delete'; + break; + case 'ctools-export-enable': + $op = 'enable'; + break; + case 'ctools-export-disable': + $op = 'disable'; + break; + } + + if (!$op) { + return; + } + + if (drush_get_option('all', FALSE)) { + $info = _ctools_drush_export_info('', TRUE); + $exportable_info = $info['exportables']; + + $all = drush_confirm(dt('Are you sure you would like to !op all exportables on the system?', + array('!op' => _ctools_drush_export_op_alias($op)))); + + if ($all && $exportable_info) { + foreach ($exportable_info as $table => $exportables) { + if (!empty($exportables)) { + ctools_drush_export_op($op, $table, $exportables); + } + } + } + } + else { + $args = func_get_args(); + // Table name should always be first arg... + $table_name = array_shift($args); + // Any additional args are assumed to be exportable names. + $object_names = $args; + + // Return any exportables based on table name, object names, options. + $exportables = _ctools_drush_export_op_command_logic($op, $table_name, $object_names); + + if ($exportables) { + ctools_drush_export_op($op, $table_name, $exportables); + } + } +} + +/** + * Iterate through exportable object names, load them, and pass each + * object to the correct op function. + * + * @param $op + * @param $table_name + * @param $exportables + * + */ +function ctools_drush_export_op($op = '', $table_name = '', $exportables = NULL) { + $objects = ctools_export_crud_load_multiple($table_name, array_keys($exportables)); + + $function = '_ctools_drush_export_' . $op; + if (function_exists($function)) { + foreach ($objects as $object) { + $function($table_name, $object); + } + } + else { + drush_log(dt('CTools CRUD function !function doesn\'t exist.', + array('!function' => $function)), 'error'); + } +} + +/** + * Helper function to abstract logic for selecting exportable types/objects + * from individual commands as they will all share this same error + * handling/arguments for returning list of exportables. + * + * @param $table_name + * @param $object_names + * + * @return + * Array of exportable objects (filtered if necessary, by name etc..) or FALSE if not. + */ +function _ctools_drush_export_op_command_logic($op = '', $table_name = NULL, array $object_names = array()) { + if (!$table_name) { + drush_log(dt('Exportable table name empty. Use the --all command if you want to perform this operation on all tables.'), 'error'); + return FALSE; + } + + // Get export info based on table name. + $info = _ctools_drush_export_info(array($table_name), TRUE); + + if (!isset($info['exportables'][$table_name])) { + drush_log(dt('Exportable table name not found.'), 'error'); + return FALSE; + } + + $exportables = &$info['exportables']; + + if (empty($object_names)) { + $all = drush_confirm(dt('No object names entered. Would you like to try and !op all exportables of type !type', + array('!op' => _ctools_drush_export_op_alias($op), '!type' => $table_name))); + if (!$all) { + drush_log(dt('Command cancelled'), 'success'); + return FALSE; + } + } + else { + // Iterate through object names and check they exist in exportables array. + // Log error and unset them if they don't. + foreach ($object_names as $object_name) { + if (!isset($exportables[$table_name][$object_name])) { + drush_log(dt('Invalid exportable: !exportable', array('!exportable' => $object_name)), 'error'); + unset($object_names[$table_name][$object_name]); + } + } + // Iterate through exportables to get just a list of selected ones. + foreach (array_keys($exportables[$table_name]) as $exportable) { + if (!in_array($exportable, $object_names)) { + unset($exportables[$table_name][$exportable]); + } + } + } + + $export_module = drush_get_option('module', FALSE); + + if (is_string($export_module)) { + $exportables = _ctools_drush_export_module_filter($exportables, $export_module); + } + + return $exportables[$table_name]; +} + +/** + * Return array of CTools exportable info based on available tables returned from + * ctools_export_get_schemas(). + * + * @param $table_names + * Array of table names to return. + * @param $load + * (bool) should ctools exportable objects be loaded for each type. + * The default behaviour will load just a list of exportable names. + * + * @return + * Nested arrays of available exportables, keyed by table name. + */ +function _ctools_drush_export_info(array $table_names = array(), $load = FALSE) { + ctools_include('export'); + // Get available schemas that declare exports. + $schemas = ctools_export_get_schemas(TRUE); + $exportables = array(); + + if (!empty($schemas)) { + // Remove types we don't want, if any. + if (!empty($table_names)) { + foreach (array_keys($schemas) as $table_name) { + if (!in_array($table_name, $table_names)) { + unset($schemas[$table_name]); + } + } + } + // Load array of available exportables for each schema. + foreach ($schemas as $table_name => $schema) { + // Load all objects. + if ($load) { + $exportables[$table_name] = ctools_export_crud_load_all($table_name); + } + // Get a list of exportable names. + else { + if (!empty($schema['export']['list callback']) && function_exists($schema['export']['list callback'])) { + $exportables[$table_name] = $schema['export']['list callback'](); + } + else { + $exportables[$table_name] = ctools_export_default_list($table_name, $schema); + } + } + } + } + + if ($load) { + $filter = drush_get_option('filter', FALSE); + $exportables = _ctools_drush_filter_exportables($exportables, $filter); + } + + return array('exportables' => $exportables, 'schemas' => $schemas); +} + +/* + * View a single object. + * + * @param $table_name + * @param $object + */ +function _ctools_drush_export_view($table_name, $object) { + $indent = drush_get_option('indent', ''); + $no_colour = drush_get_option('no-colour', FALSE); + $export = ctools_export_crud_export($table_name, $object, $indent); + if ($no_colour) { + drush_print("\n$export"); + } + else { + drush_print(shellColours::getColouredOutput("\n$export", 'light_green')); + } +} + +/* + * Revert a single object. + * + * @param $table_name + * @param $object + */ +function _ctools_drush_export_delete($table_name, $object) { + $name = _ctools_drush_get_export_name($table_name, $object); + + if (_ctools_drush_object_is_overridden($object)) { + // Remove from db. + ctools_export_crud_delete($table_name, $object); + drush_log("Reverted object: $name", 'success'); + } + else { + drush_log("Nothing to revert for: $name", 'notice'); + } +} + +/* + * Enable a single object. + * + * @param $table_name + * @param $object + */ +function _ctools_drush_export_enable($table_name, $object) { + $name = _ctools_drush_get_export_name($table_name, $object); + + if (_ctools_drush_object_is_disabled($object)) { + + // Enable object. + ctools_export_crud_enable($table_name, $object); + drush_log("Enabled object: $name", 'success'); + } + else { + drush_log("$name is already Enabled", 'notice'); + } +} + +/* + * Disable a single object. + * + * @param $table_name + * @param $object + */ +function _ctools_drush_export_disable($table_name, $object) { + $name = _ctools_drush_get_export_name($table_name, $object); + + if (!_ctools_drush_object_is_disabled($object)) { + // Disable object. + ctools_export_crud_disable($table_name, $object); + drush_log("Disabled object: $name", 'success'); + } + else { + drush_log("$name is already disabled", 'notice'); + } +} + +/** + * Filter a nested array of exportables by export module. + * + * @param $exportables array + * Passed by reference. A nested array of exportables, keyed by table name. + * @param $export_module string + * The name of the export module providing the exportable. + */ +function _ctools_drush_export_module_filter($exportables, $export_module) { + $module_list = module_list(); + + if (!isset($module_list[$export_module])) { + drush_log(dt('Invalid export module: !export_module', array('!export_module' => $export_module)), 'error'); + } + + foreach ($exportables as $table => $objects) { + foreach ($objects as $key => $object) { + if (empty($object->export_module) || ($object->export_module !== $export_module)) { + unset($exportables[$table][$key]); + } + } + } + + return array_filter($exportables); +} + +/** + * Gets the key for an exportable type. + * + * @param $table_name + * The exportable table name. + * @param $object + * The exportable object. + * + * @return string + * The key defined in the export schema data. + */ +function _ctools_drush_get_export_name($table_name, $object) { + $info = _ctools_drush_export_info(array($table_name)); + $key = $info['schemas'][$table_name]['export']['key']; + return $object->{$key}; +} + +/** + * Determine if an object is disabled. + * + * @param $object + * Loaded CTools exportable object. + * + * @return TRUE or FALSE + */ +function _ctools_drush_object_is_disabled($object) { + return (isset($object->disabled) && ($object->disabled == TRUE)) ? TRUE : FALSE; +} + +/** + * Determine if an object is enabled. + * + * @see _ctools_drush_object_is_disabled. + */ +function _ctools_drush_object_is_enabled($object) { + return (empty($object->disabled)) ? TRUE : FALSE; +} + +/** + * Determine if an object is overridden. + */ +function _ctools_drush_object_is_overridden($object) { + $status = EXPORT_IN_CODE + EXPORT_IN_DATABASE; + return ($object->export_type == $status) ? TRUE : FALSE; +} + +/** + * Determine if an object is not overridden. + */ +function _ctools_drush_object_is_not_overridden($object) { + $status = EXPORT_IN_CODE + EXPORT_IN_DATABASE; + return ($object->export_type == $status) ? FALSE : TRUE; +} + +/** + * Determine if an object is only in the db. + */ +function _ctools_drush_object_is_db_only($object) { + return ($object->export_type == EXPORT_IN_DATABASE) ? TRUE : FALSE; +} + +/** + * Determine if an object is not in the db. + */ +function _ctools_drush_object_is_not_db_only($object) { + return ($object->export_type == EXPORT_IN_DATABASE) ? FALSE : TRUE; +} + +/** + * Determine if an object is a code only default. + */ +function _ctools_drush_object_is_code_only($object) { + return ($object->export_type == EXPORT_IN_CODE) ? TRUE : FALSE; +} + +/** + * Determine if an object is not a code only default. + */ +function _ctools_drush_object_is_not_code_only($object) { + return ($object->export_type == EXPORT_IN_CODE) ? FALSE : TRUE; +} + +/** + * Return an array of count information based on exportables array. + * + * @param $exportables + * Array of exportables to count. + * + * @return + * Array of count data containing the following: + * 'total' - A total count of all exportables. + * 'exportables' - An array of exportable counts per table. + */ +function _ctools_drush_count_exportables($exportables) { + $count = array('exportables' => array()); + + foreach ($exportables as $table => $objects) { + // Add the object count for each table. + $count['exportables'][$table] = count($objects); + } + + // Once all tables have been counted, total these up. + $count['total'] = array_sum($count['exportables']); + + return $count; +} + +/** + * Filters a collection of exportables based on filters. + * + * @param $exportables + * @param $filter + */ +function _ctools_drush_filter_exportables($exportables, $filter) { + $eval = FALSE; + + if (is_string($filter)) { + switch ($filter) { + // Show enabled exportables only. + case 'enabled': + $eval = '_ctools_drush_object_is_disabled'; + break; + // Show disabled exportables only. + case 'disabled': + $eval = '_ctools_drush_object_is_enabled'; + break; + // Show overridden exportables only. + case 'overridden': + $eval = '_ctools_drush_object_is_not_overridden'; + break; + // Show database only exportables. + case 'database': + $eval = '_ctools_drush_object_is_not_db_only'; + break; + // Show code only exportables. + case 'code': + $eval = '_ctools_drush_object_is_not_code_only'; + break; + // Do nothing. + case 'all': + break; + default: + drush_log(dt('Invalid filter option. Available options are: enabled, disabled, overridden, database, and code.'), 'error'); + return; + } + + if ($eval) { + foreach ($exportables as $table => $objects) { + foreach ($objects as $key => $object) { + if ($eval($object)) { + unset($exportables[$table][$key]); + } + } + } + } + } + + return array_filter($exportables); +} + +/** + * Return an alias for an op, that will be used to show as output. + * For now, this is mainly necessary for delete => revert alias. + * + * @param $op + * The op name. Such as 'enable', 'disable', or 'delete'. + * + * @return + * The matched alias value or the original $op passed in if not found. + */ +function _ctools_drush_export_op_alias($op) { + $aliases = array( + 'delete' => 'revert', + ); + + if (isset($aliases[$op])) { + return $aliases[$op]; + } + + return $op; +} + +/** + * Convert the drush options from a csv list into an array. + * + * @param $drush_option + * The drush option name to invoke. + * + * @return + * Exploded array of options. + */ +function _ctools_drush_explode_options($drush_option) { + $options = drush_get_option($drush_option, array()); + if (!empty($options)) { + $options = explode(',', $options); + return array_map('trim', $options); + } + + return $options; +} + +/** + * Class to deal with wrapping output strings with + * colour formatting for the shell. + */ +class shellColours { + + private static $foreground_colours = array( + 'black' => '0;30', + 'dark_gray' => '1;30', + 'blue' => '0;34', + 'light_blue' => '1;34', + 'green' => '0;32', + 'light_green' => '1;32', + 'cyan' => '0;36', + 'light_cyan' => '1;36', + 'red' => '0;31', + 'light_red' => '1;31', + 'purple' => '0;35', + 'light_purple' => '1;35', + 'brown' => '0;33', + 'yellow' => '1;33', + 'light_gray' => '0;37', + 'white' => '1;37', + ); + + private static $background_colours = array( + 'black' => '40', + 'red' => '41', + 'green' => '42', + 'yellow' => '43', + 'blue' => '44', + 'magenta' => '45', + 'cyan' => '46', + 'light_gray' => '47', + ); + + private function __construct() {} + + // Returns coloured string + public static function getColouredOutput($string, $foreground_colour = NULL, $background_colour = NULL) { + $coloured_string = ""; + + // Check if given foreground colour found + if ($foreground_colour) { + $coloured_string .= "\033[" . self::$foreground_colours[$foreground_colour] . "m"; + } + // Check if given background colour found + if ($background_colour) { + $coloured_string .= "\033[" . self::$background_colours[$background_colour] . "m"; + } + + // Add string and end colouring + $coloured_string .= $string . "\033[0m"; + + return $coloured_string; + } + + // Returns all foreground colour names + public static function getForegroundColours() { + return array_keys(self::$foreground_colours); + } + + // Returns all background colour names + public static function getBackgroundColours() { + return array_keys(self::$background_colours); + } + +} // shellColours diff --git a/sites/all/modules/contrib/ctools/help/about.html b/sites/all/modules/contrib/ctools/help/about.html new file mode 100644 index 0000000..30b64c2 --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/about.html @@ -0,0 +1,29 @@ +The Chaos Tool Suite is a series of tools for developers to make code that I've found to be very useful to Views and Panels more readily available. Certain methods of doing things, particularly with AJAX, exportable objects and a plugin system, are proving to be ideas that are useful outside of just Views and Panels. This module does not offer much directly to the end user, but instead, creates a library for other modules to use. If you are an end user and some module asked you to install the CTools suite, then this is far as you really need to go. If you're a developer and are interested in these tools, read on!
+ +Tools provided by CTools
+ ++
diff --git a/sites/all/modules/contrib/ctools/help/ajax.html b/sites/all/modules/contrib/ctools/help/ajax.html new file mode 100644 index 0000000..e69de29 diff --git a/sites/all/modules/contrib/ctools/help/collapsible-div.html b/sites/all/modules/contrib/ctools/help/collapsible-div.html new file mode 100644 index 0000000..b9b6d9c --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/collapsible-div.html @@ -0,0 +1 @@ +- Plugins
+- The plugins tool allows a module to allow other modules (and themes!) to provide plugins which provide some kind of functionality or some kind of task. For example, in Panels there are several types of plugins: Content types (which are like blocks), layouts (which are page layouts) and styles (which can be used to style a panel). Each plugin is represented by a .inc file, and the functionality they offer can differ wildly.
+ +- Context
+- Context is the idea that the objects that are used in page generation have more value than simply creating a single piece of output. Instead, contexts can be used to create multiple pieces of content that can all be put onto the page. Additionally, contexts can be used to derive other contexts via relationships, such as determining the node author and displaying data about the new context.
+ +- AJAX Tools
+- AJAX (also known as AHAH) is a method of allowing the browser and the server to communicate without requiring a page refresh. It can be used to create complicated interactive forms, but it is somewhat difficult to integrate into Drupal's Form API. These tools make it easier to accomplish this goal. In addition, CTools provides a few other javascript helpers, such as a modal dialog, a collapsible div, a simple dropdown and dependent checkboxes.
+ +- CSS scrubbing and caching
+- Drupal comes with a fantastic array of tools to ensure HTML is safe to output but does not contain any similar tools for CSS. CTools provides a small tool to sanitize CSS, so user-input CSS code can still be safely used. It also provides a method for caching CSS for better performance.
+ +- Exportable objects
+- Views and Panels both use objects that can either be in code or in the database, and the objects can be exported into a piece of PHP code, so they can be moved from site to site or out of the database entirely. This library abstracts that functionality, so other modules can use this same concept for their data.
+ +- Form tools
+- Drupal 6's FAPI really improved over Drupal 5, and made a lot of things possible. Still, it missed a few items that were needed to make form wizards and truly dynamic AJAX forms possible. CTools includes a replacement for drupal_get_form() that has a few more options and allows the caller to examine the $form_state once the form has completed.
+ +- Form wizards
+- Finally! An easy way to have form wizards, which is any 'form' that is actually a string of forms that build up to a final conclusion. The form wizard supports a single entry point, the ability to choose whether or not the user can go forward/back/up on the form and easy callbacks to handle the difficult job of dealing with data in between forms.
+ +- Temporary object cache
+- For normal forms, all of the data needed for an object is stored in the form so that the browser handles a lot of the work. For multi-step and ajax forms, however, this is impractical, and letting the browser store data can be insecure. The object cache provides a non-volatile location to store temporary data while the form is being worked on. This is much safer than the standard Drupal caching mechanism, which is volatile, meaning it can be cleared at any time and any system using it must be capable of recreating the data that was there. This system also allows for object locking, since any object which has an item in the cache from another person can be assumed to be 'locked for editing'.
+To be written.
diff --git a/sites/all/modules/contrib/ctools/help/context-access.html b/sites/all/modules/contrib/ctools/help/context-access.html new file mode 100644 index 0000000..95a8d7f --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/context-access.html @@ -0,0 +1,12 @@ +Access plugins allow context based access control to pages.
+'title' => Title of the plugin + 'description' => Description of the plugin + 'callback' => callback to see if there is access is available. params: $conf, $contexts, $account + 'required context' => zero or more required contexts for this access plugin + 'default' => an array of defaults or a callback giving defaults + 'settings form' => settings form. params: &$form, &$form_state, $conf + settings form validate + settings form submit ++ +Warning: your settings array will be stored in the meny system to reduce loads, so be trim.
\ No newline at end of file diff --git a/sites/all/modules/contrib/ctools/help/context-arguments.html b/sites/all/modules/contrib/ctools/help/context-arguments.html new file mode 100644 index 0000000..5c479ae --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/context-arguments.html @@ -0,0 +1,14 @@ +Arguments create a context from external input, which is assumed to be a string as though it came from a URL element.
+ +'title' => title + 'description' => Description + 'keyword' => Default keyword for the context + 'context' => Callback to create the context. Params: $arg = NULL, $conf = NULL, $empty = FALSE + 'default' => either an array of default settings or a string which is a callback or null to not use. + 'settings form' => params: $form, $form_state, $conf -- gets the whole form. Should put anything it wants to keep automatically in $form['settings'] + 'settings form validate' => params: $form, $form_state + 'settings form submit' => params: $form, $form_state + 'criteria form' => params: $form, &$form_state, $conf, $argument, $id -- gets the whole argument. It should only put form widgets in $form[$id]. $conf may not be properly initialized so always guard against this due to arguments being changed and handlers not being updated to match. + + submit + validate + 'criteria select' => returns true if the selected criteria matches the context. params: $context, $conf +diff --git a/sites/all/modules/contrib/ctools/help/context-content.html b/sites/all/modules/contrib/ctools/help/context-content.html new file mode 100644 index 0000000..c1c6a35 --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/context-content.html @@ -0,0 +1,157 @@ +The CTools pluggable content system provides various pieces of content as discrete bits of data that can be added to other applications, such as Panels or Dashboard via the UI. Whatever the content is added to stores the configuration for that individual piece of content, and provides this to the content.
+ +Each content type plugin will be contained in a .inc file, with subsidiary files, if necessary, in or near the same directory. Each content type consists of some information and one or more subtypes, which all use the same renderer. Subtypes are considered to be instances of the type. For example, the 'views' content type would have each view in the system as a subtype. Many content types will have exactly one subtype.
+ +Because the content and forms can be provided via ajax, the plugin also provides a list of CSS and JavaScript information that should be available on whatever page the content or forms may be AJAXed onto.
+ +For the purposes of selecting content from the UI, each content subtype will have the following information:
+ ++
+ +- A title
+- A short description
+- A category [Do we want to add hierarchy categories? Do we want category to be more than just a string?]
+- An icon [do we want multiple icons? This becomes a hefty requirement]
+Each piece of content provides one or more configuration forms, if necessary, and the system that includes the content will handle the data storage. These forms can be provided in sequence as wizards or as extra forms that can be accessed through advanced administration.
+ +The plugin for a content type should contain:
+ ++
+ +- title
+- For use on the content permissions screen.
+- content types
+- Either an array of content type definitions, or a callback that will return content type definitions. This callback will get the plugin definition as an argument.
+ +- content type
+- [Optional] Provide a single content type definition. This is only necessary if content types might be intensive.
+ +- render callback
+- The callback to render the content. Parameters: +
+ ++
+- $subtype
+- The name of the subtype being rendered. NOT the loaded subtype data.
+ +- $conf
+- The stored configuration for the content.
+ +- $args
+- Any arguments passed.
+ +- $context
+- An array of contexts requested by the required contexts and assigned by the configuration step.
+ +- $incoming_content
+- Any 'incoming content' if this is a wrapper.
+- admin title
+- A callback to provide the administrative title. If it is not a function, then it will be counted as a string to use as the admin title.
+ +- admin info
+- A callback to provide administrative information about the content, to be displayed when manipulating the content. It should contain a summary of configuration.
+ +- edit form
+- Either a single form ID or an array of forms *keyed* by form ID with the value to be used as the title of the form. %title me be used as a placeholder for the administrative title if necessary. + Example: +
+ +array( + 'ctools_example_content_form_second' => t('Configure first form'), + 'ctools_example_content_form_first' => t('Configure second form'), +), ++The first form will always have required configuration added to it. These forms are normal FAPI forms, but you do not need to provide buttons, these will be added by the form wizard. +- add form
+- [Optional] If different from the edit forms, provide them here in the same manner. Also may be set to FALSE to not have an add form.
+ +- css
+- A file or array of CSS files that are necessary for the content.
+ +- js
+- A file or array of javascript files that are necessary for the content to be displayed.
+ +- admin css
+- A file or array of CSS files that are necessary for the forms.
+ +- admin js
+- A file or array of JavaScript files that are necessary for the forms.
+ +- extra forms
+- An array of form information to handle extra administrative forms.
+ +- no title override
+- Set to TRUE if the title cannot be overridden.
+ +- single
+- Set to TRUE if this content provides exactly one subtype.
+ +- render last
+- Set to true if for some reason this content needs to render after other content. This is primarily used for forms to ensure that render order is correct.
+TODO: many of the above callbacks can be assumed based upon patterns: modulename + '_' + name + '_' + function. i.e, render, admin_title, admin_info, etc.
+ +TODO: Some kind of simple access control to easily filter out content.
+ +The subtype definition should contain:
+ ++
+ +- title
+- The title of the subtype.
+ +- icon
+- The icon to display for the subtype.
+ +- path
+- The path for the icon if it is not in the same directory as the plugin.
+ +- description
+- The short description of the subtype, to be used when selecting it in the UI.
+ +- category
+- Either a text string for the category, or an array of the text string followed by the category weight.
+ +- required context [Optional]
+ +- Either a ctools_context_required or ctools_context_optional or array of contexts for this content. If omitted, no contexts are used.
+ +- create content access [Optional]
+ +- An optional callback to determine if a user can access this subtype. The callback will receive two arguments, the type and subtype.
+Rendered content
+ +Rendered content is a little more than just HTML.
+ ++
+ +- title
+- The safe to render title of the content.
+ +- content
+- The safe to render HTML content.
+ +- links
+- An array of links associated with the content suitable for theme('links').
+ +- more
+- An optional 'more' link (destination only)
+ +- admin_links
+- Administrative links associated with the content, suitable for theme('links').
+ +- feeds
+- An array of feed icons or links associated with the content. Each member of the array is rendered HTML.
+ +- type
+- The content type.
+ +- subtype
+- The content subtype. These two may be used together as module-delta for block style rendering.
+Todo: example
+ +Todo after implementations are updated to new version.
diff --git a/sites/all/modules/contrib/ctools/help/context-context.html b/sites/all/modules/contrib/ctools/help/context-context.html new file mode 100644 index 0000000..2314bd5 --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/context-context.html @@ -0,0 +1,13 @@ +Context plugin data:
+ ++ 'title' => Visible title + 'description' => Description of context + 'context' => Callback to create a context. Params: $empty, $data = NULL, $conf = FALSE + 'settings form' => Callback to show a context setting form. Params: ($conf, $external = FALSE) + 'settings form validate' => params: ($form, &$form_values, &$form_state) + 'settings form submit' => params: 'ctools_context_node_settings_form_submit', + 'keyword' => The default keyword to use. + 'context name' => The unique identifier for this context for use by required context checks. + 'no ui' => if TRUE this context cannot be selected. +\ No newline at end of file diff --git a/sites/all/modules/contrib/ctools/help/context-relationships.html b/sites/all/modules/contrib/ctools/help/context-relationships.html new file mode 100644 index 0000000..cc9969e --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/context-relationships.html @@ -0,0 +1,13 @@ +Relationship plugin data:
+ ++ 'title' => The title to display. + 'description' => Description to display. + 'keyword' => Default keyword for the context created by this relationship. + 'required context' => One or more ctools_context_required/optional objects + describing the context input. + new panels_required_context(t('Node'), 'node'), + 'context' => The callback to create the context. Params: ($context = NULL, $conf) + 'settings form' => Settings form. Params: $conf + 'settings form validate' => Validate. +diff --git a/sites/all/modules/contrib/ctools/help/context.html b/sites/all/modules/contrib/ctools/help/context.html new file mode 100644 index 0000000..e69de29 diff --git a/sites/all/modules/contrib/ctools/help/css.html b/sites/all/modules/contrib/ctools/help/css.html new file mode 100644 index 0000000..b9b6d9c --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/css.html @@ -0,0 +1 @@ +To be written.
diff --git a/sites/all/modules/contrib/ctools/help/ctools.help.ini b/sites/all/modules/contrib/ctools/help/ctools.help.ini new file mode 100644 index 0000000..fcb121b --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/ctools.help.ini @@ -0,0 +1,97 @@ +[advanced help settings] +line break = TRUE + +[about] +title = About Chaos Tool Suite +weight = -100 + +[context] +title = Context tool +weight = -40 + +[context-access] +title = Context based access control plugins +parent = context + +[context-context] +title = Context plugins +parent = context + +[context-arguments] +title = Argument plugins +parent = context + +[context-relationships] +title = Relationship plugins +parent = context + +[context-content] +title = Content plugins +parent = context + +[css] +title = CSS scrubbing and caching tool + +[menu] +title = Miscellaneous menu helper tool + +[plugins] +title = Plugins and APIs tool +weight = -50 + +[plugins-api] +title = Implementing APIs +parent = plugins + +[plugins-creating] +title = Creating plugins +parent = plugins + +[plugins-implementing] +title = Implementing plugins +parent = plugins + +[export] +title = Exportable objects tool + +[export-ui] +title = Exportable objects UI creator + +[form] +title = Form tools + +[wizard] +title = Form wizard tool + +[ajax] +title = AJAX and Javascript helper tools +weight = -30 + +[modal] +title = Javascript modal tool +parent = ajax + +[collapsible-div] +title = Javascript collapsible DIV +parent = ajax + +[dropdown] +title = Javascript dropdown +parent = ajax + +[dropbutton] +title = Javascript dropbutton +parent = ajax + +[dependent] +title = Dependent checkboxes and radio buttons +parent = ajax + +[object-cache] +title = Temporary object caching + +; A bunch of this stuff we'll put in panels. + +[plugins-content] +title = Creating content type plugins +parent = panels%api diff --git a/sites/all/modules/contrib/ctools/help/dependent.html b/sites/all/modules/contrib/ctools/help/dependent.html new file mode 100644 index 0000000..b9b6d9c --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/dependent.html @@ -0,0 +1 @@ +To be written.
diff --git a/sites/all/modules/contrib/ctools/help/dropbutton.html b/sites/all/modules/contrib/ctools/help/dropbutton.html new file mode 100644 index 0000000..b9b6d9c --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/dropbutton.html @@ -0,0 +1 @@ +To be written.
diff --git a/sites/all/modules/contrib/ctools/help/dropdown.html b/sites/all/modules/contrib/ctools/help/dropdown.html new file mode 100644 index 0000000..b9b6d9c --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/dropdown.html @@ -0,0 +1 @@ +To be written.
diff --git a/sites/all/modules/contrib/ctools/help/export-ui.html b/sites/all/modules/contrib/ctools/help/export-ui.html new file mode 100644 index 0000000..e6b1086 --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/export-ui.html @@ -0,0 +1,85 @@ +Most user interfaces for exportables are very similar, so CTools includes a tool to provide the framework for the most common UI. This tool is a plugin of the 'export_ui' type. In order to create a UI for your exportable object with this tool, you first need to ensure that your module supports the plugin:
+ ++function HOOK_ctools_plugin_directory($module, $plugin) { + if ($module == 'ctools' && $plugin == 'export_ui') { + return 'plugins/' . $plugin; + } +} ++ +Then, you need to create a plugin .inc file describing your UI. Most of the UI runs with sane but simple defaults, so for the very simplest UI you don't need to do very much. This is a very simple example plugin for the 'example' export type:
+ ++$plugin = array( + // The name of the table as found in the schema in hook_install. This + // must be an exportable type with the 'export' section defined. + 'schema' => 'example', + + // The access permission to use. If not provided it will default to + // 'administer site configuration' + 'access' => 'administer example', + + // You can actually define large chunks of the menu system here. Nothing + // is required here. If you leave out the values, the prefix will default + // to admin/structure and the item will default to the plugin name. + 'menu' => array( + 'menu prefix' => 'admin/structure', + 'menu item' => 'example', + // Title of the top level menu. Note this should not be translated, + // as the menu system will translate it. + 'menu title' => 'Example', + // Description of the top level menu, which is usually needed for + // menu items in an administration list. Will be translated + // by the menu system. + 'menu description' => 'Administer site example objects.', + ), + + // These are required to provide proper strings for referring to the + // actual type of exportable. "proper" means it will appear at the + // beginning of a sentence. + 'title singular' => t('example'), + 'title singular proper' => t('Example'), + 'title plural' => t('examples'), + 'title plural proper' => t('Examples'), + + // This will provide you with a form for editing the properties on your + // exportable, with validate and submit handler. + // + // The item being edited will be in $form_state['item']. + // + // The submit handler is only responsible for moving data from + // $form_state['values'] to $form_state['item']. + // + // All callbacks will accept &$form and &$form_state as arguments. + 'form' => array( + 'settings' => 'example_ctools_export_ui_form', + 'validate' => 'example_ctools_export_ui_form_validate', + 'submit' => 'example_ctools_export_ui_form_submit', + ), + +); ++ +For a more complete list of what you can set in your plugin, please see ctools_export_ui_defaults() in includes/export-ui.inc to see what the defaults are.
+ +More advanced UIs
+ +The bulk of this UI is built on an class called ctools_export_ui, which is itself stored in ctools/plugins/export_ui as the default plugin. Many UIs will have more complex needs than the defaults provide. Using OO and overriding methods can allow an implementation to use the basics and still provide more where it is needed. To utilize this, first add a 'handler' directive to your plugin .inc file:
+ ++ 'handler' => array( + 'class' => 'ctools_export_ui_example', + 'parent' => 'ctools_export_ui', + ), ++ +Then create your class in ctools_export_ui_example.class.php in your plugins directory: + ++class ctools_export_ui_example extends ctools_export_ui { + +} ++ +You can override any method found in the class (see the source file for details). In particular, there are several list methods that are good candidates for overriding if you need to provide richer data for listing, sorting or filtering. If you need multi-step add/edit forms, you can override edit_page(), add_page(), clone_page(), and import_page() to put your wizard in place of the basic editing system. For an example of how to use multi-step wizards, see the import_page() method.
diff --git a/sites/all/modules/contrib/ctools/help/export.html b/sites/all/modules/contrib/ctools/help/export.html new file mode 100644 index 0000000..ce24cad --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/export.html @@ -0,0 +1,294 @@ +Exportable objects are objects that can live either in the database or in code, or in both. If they live in both, then the object in code is considered to be "overridden", meaning that the version in code is ignored in favor of the version in the database.
+ +The main benefit to this is that you can move objects that are intended to be structure or feature-related into code, thus removing them entirely from the database. This is a very important part of the deployment path, since in an ideal world, the database is primarily user generated content, whereas site structure and site features should be in code. However, many many features in Drupal rely on objects being in the database and provide UIs to create them.
+ +Using this system, you can give your objects dual life. They can be created in the UI, exported into code and put in revision control. Views and Panels both use this system heavily. Plus, any object that properly implements this system can be utilized by the Features module to be used as part of a bundle of objects that can be turned into feature modules.
+ +Typically, exportable objects have two identifiers. One identifier is a simple serial used for database identification. It is a primary key in the database and can be used locally. It also has a name which is an easy way to uniquely identify it. This makes it much less likely that importing and exporting these objects across systems will have collisions. They still can, of course, but with good name selection, these problems can be worked around.
+ +Making your objects exportable
+ +To make your objects exportable, you do have to do a medium amount of work.
+ ++
+ +- Create a chunk of code in your object's schema definition in the .install file to introduce the object to CTools' export system.
+- Create a load function for your object that utilizes ctools_export_load_object().
+- Create a save function for your object that utilizes drupal_write_record() or any method you desire.
+- Create an import and export mechanism from the UI.
+The export section of the schema file
+ +Exportable objects are created by adding definition to the schema in an 'export' section. For example:
+ ++function mymodule_schema() { + $schema['mymodule_myobj'] = array( + 'description' => t('Table storing myobj definitions.'), + 'export' => array( + 'key' => 'name', + 'key name' => 'Name', + 'primary key' => 'oid', + 'identifier' => 'myobj', // Exports will be as $myobj + 'default hook' => 'default_mymodule_myobj', // Function hook name. + 'api' => array( + 'owner' => 'mymodule', + 'api' => 'default_mymodule_myobjs', // Base name for api include files. + 'minimum_version' => 1, + 'current_version' => 1, + ), + // If the key is stored in a table that is joined in, specify it: + 'key in table' => 'my_join_table', + + ), + + // If your object's data is split up across multiple tables, you can + // specify additional tables to join. This is very useful when working + // with modules like exportables.module that has a special table for + // translating keys to local database IDs. + // + // The joined table must have its own schema definition. + // + // If using joins, you should implement a 'delete callback' (see below) + // to ensure that deletes happen properly. export.inc does not do this + // automatically! + 'join' => array( + 'exportables' => array( + // The following parameters will be used in this way: + // SELECT ... FROM {mymodule_myobj} t__0 INNER JOIN {my_join_table} t__1 ON t__0.id = t__1.id AND extras + 'table' => 'my_join_table', + 'left_key' => 'format', + 'right_key' => 'id', + // Optionally you can define a callback to add custom conditions or + // alter the query as necessary. The callback function takes 3 args: + // + // myjoincallback(&$query, $schema, $join_schema); + // + // where $query is the database query object, $schema is the schema for + // the export base table and $join_schema is the schema for the current + // join table. + 'callback' => 'myjoincallback', + + // You must specify which fields will be loaded. These fields must + // exist in the schema definition of the joined table. + 'load' => array( + 'machine', + ), + + // And finally you can define other tables to perform INNER JOINS + //'other_joins' => array( + // 'table' => ... + //), + ), + ) + 'fields' => array( + 'name' => array( + 'type' => 'varchar', + 'length' => '255', + 'description' => 'Unique ID for this object. Used to identify it programmatically.', + ), + 'oid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Primary ID field for the table. Not used for anything except internal lookups.', + 'no export' => TRUE, // Do not export database-only keys. + ), + // ...... + 'primary key' => array('oid'), + 'unique keys' => array( + 'name' => array('name'), + ), + ); + return $schema; +} ++ ++
+- key
+- This is the primary key of the exportable object and should be a string as names are more portable across systems. It is possible to use numbers here, but be aware that export collisions are very likely. Defaults to 'name'.
+ +- key name
+- Human readable title of the export key. Defaults to 'Name'. Because the schema is cached, do not translate this. It must instead be translated when used.
+ +- primary key
+- A single field within the table that is to be used as the main identifier to discern whether or not the object has been written. As the schema definition's primary key value will be used by default, it is not usually necessary to define this.
+ +- object
+- The class the object should be created as, if 'object factory' is not set. If this is not set either, defaults as stdClass.
+ +- object factory
+- Function used to create the object. The function receives the schema and the loaded data as a parameters: your_factory_function($schema, $data). If this is set, 'object' has no effect since you can use your function to create whatever class you wish.
+ +- admin_title
+- A convenience field that may contain the field which represents the human readable administrative title for use in export UI. If a field "admin_title" exists, it will automatically be used.
+ +- admin_description
+- A convenience field that may contain the field which represents the human readable administrative title for use in export UI. If a field "admin_title" exists, it will automatically be used.
+ +- can disable
+- Control whether or not the exportable objects can be disabled. All this does is cause the 'disabled' field on the object to always be set appropriately, and a variable is kept to record the state. Changes made to this state must be handled by the owner of the object. Defaults to TRUE.
+ +- status
+- Exportable objects can be enabled or disabled, and this status is stored in a variable. This defines what variable that is. Defaults to: 'default_' . $table.
+ +- default hook
+- What hook to invoke to find exportable objects that are currently defined. These will all be gathered into a giant array. Defaults to 'default_' . $table.
+ +- cache defaults
+- If true, default objects will be cached so that the processing of the hook does not need to be called often. Defaults to FALSE. Recommended if you will potentially have a lot of objects in code. Not recommended if code will be the exception.
+ +- default cache bin
+- If default object caching is enabled, what cache bin to use. This defaults to the basic "cache". It is highly recommended that you use a different cache bin if possible.
+ +- identifier
+- When exporting the object, the identifier is the variable that the exported object will be placed in. Defaults to $table.
+ +- bulk export
+- Declares whether or not the exportable will be available for bulk exporting.
+ +- export type string
+- The export type string (Local, Overridden, Database) is normally stored as $item->type. Since type is a very common keyword, it's possible to specify what key to actually use.
+ +- list callback
+- Bulk export callback to provide a list of exportable objects to be chosen for bulk exporting. Defaults to $module . '_' . $table . '_list' if the function exists. If it is not, a default listing function will be provided that will make a best effort to list the titles. See ctools_export_default_list().
+ +- to hook code callback
+- Function used to generate an export for the bulk export process. This is only necessary if the export is more complicated than simply listing the fields. Defaults to $module . '_' . $table . '_to_hook_code'. + +
- boolean
+- Explicitly indicate if a table field contains a boolean or not. The Schema API does not model the +difference between a tinyint and a boolean type. Boolean values are stored in tinyint fields. This may cause mismatch errors when exporting a non-boolean value from a tinyint field. Add this to a tinyint field if it contains boolean data and can be exported. Defaults to TRUE. + +
- create callback
+- CRUD callback to use to create a new exportable item in memory. If not provided, the default function will be used. The single argument is a boolean used to determine if defaults should be set on the object. This object will not be written to the database by this callback.
+ +- load callback
+- CRUD callback to use to load a single item. If not provided, the default load function will be used. The callback will accept a single argument which should be an identifier of the export key.
+ +- load multiple callback
+- CRUD callback to use to load multiple items. If not provided, the default multiple load function will be used. The callback will accept an array which should be the identifiers of the export key.
+ +- load all callback
+- CRUD callback to use to load all items, usually for administrative purposes. If not provided, the default load function will be used. The callback will accept a single argument to determine if the load cache should be reset or not.
+ +- save callback
+- CRUD callback to use to save a single item. If not provided, the default save function will be used. The callback will accept a single argument which should be the complete exportable object to save.
+ +- delete callback
+- CRUD callback to use to delete a single item. If not provided, the default delete function will be used. The callback will accept a single argument which can be *either* the object or just the export key to delete. The callback MUST be able to accept either.
+ +- export callback
+- CRUD callback to use for exporting. If not provided, the default export function will be used. The callback will accept two arguments, the first is the item to export, the second is the indent to place on the export, if any.
+ +- import callback
+- CRUD callback to use for importing. If not provided, the default export function will be used. This function will accept the code as a single argument and, if the code evaluates, return an object represented by that code. In the case of failure, this will return a string with human readable errors.
+ +- status callback
+- CRUD callback to use for updating the status of an object. If the status is TRUE the object will be disabled. If the status is FALSE the object will be enabled.
+ +- api
+- The 'api' key can optionally contain some information for the plugin API definition. This means that the imports can be tied to an API name which is used to have automatic inclusion of files, and can be used to prevent dangerous objects from older versions from being loaded, causing a loss of functionality rather than site crashes or security loopholes. + +
+ +If not present, no additional files will be loaded and the default hook will always be a simple hook that must be either part of the .module file or loaded during normal operations.
+ +api supports these subkeys:
+ ++
+- owner
+- The module that owns the API. Typically this is the name of the module that owns the schema. This will be one of the two keys used by hook_ctools_plugin_api() to determine version compatibility. Note that the name of this hook can be tailored via the use of hook_ctools_plugin_api_hook_name(). See ctools_plugin_api_get_hook() for details.
+- api
+- This is the name of the API, and will be the second parameter to the above mentioned hook. It will also be used as part of the name of the file that the hook containing default objects will be in, which comes in the form of MODULENAME.API.inc.
+- minimum_version
+- The minimum version supported. Any module reporting an API less than this will not have its default objects used. This should be updated only when API changes can cause older objects to crash or otherwise break badly.
+- current_version
+- The current version of the API. Any module reporting a required API higher than this will not have its default objects used.
+In addition, each field can contain the following:
++
+ +- no export
+- Set to TRUE to prevent that field from being exported.
+ +- export callback
+- A function to override the export behavior. It will receive ($myobject, $field, $value, $indent) as arguments. By default, fields are exported through ctools_var_export().
+Reserved keys on exportable objects
+ +Exportable objects have several reserved keys that are used by the CTools export API. Each key can be found at
+$myobj->{$key}on an object loaded throughctools_export_load_object(). Implementing modules should not use these keys as they will be overwritten by the CTools export API.+
+ +- api_version
+- The API version that this object implements.
+ +- disabled
+- A boolean for whether the object is disabled.
+ +- export_module
+- For objects that live in code, the module which provides the default object.
+ +- export_type
+- A bitmask representation of an object current storage. You can use this bitmask in combination with the
+ +EXPORT_IN_CODEandEXPORT_IN_DATABASEconstants to test for an object's storage in your code. +- in_code_only
+- A boolean for whether the object lives only in code.
+ +- table
+- The schema API table that this object belongs to.
+ +- type
+- A string representing the storage type of this object. Can be one of the following: +
++
+Note: This key can be changed by setting 'export type string' to something else, to try and prevent "type" from conflicting. +- Normal is an object that lives only in the database.
+- Overridden is an object that lives in the database and is overriding the exported configuration of a corresponding object in code.
+- Default is an object that lives only in code.
+The load callback
+Calling ctools_export_crud_load($table, $name) will invoke your load callback, calling ctools_export_crud_load_multiple($table, $names) will invoke your load multiple callback, and calling ctools_export_crud_load_all($table, $reset) will invoke your load all callback. The default handlers should be sufficient for most uses.
+ +Typically, there will be three load functions. A 'single' load, to load just one object, a 'multiple' load to load multiple objects, and an 'all' load, to load all of the objects for use in administrating the objects or utilizing the objects when you need all of them. Using ctools_export_load_object() you can easily do both, as well as quite a bit in between. This example duplicates the default functionality for loading one myobj.
+ ++/** + * Implements 'load callback' for myobj exportables. + */ +function mymodule_myobj_load($name) { + ctools_include('export'); + $result = ctools_export_load_object('mymodule_myobjs', 'names', array($name)); + if (isset($result[$name])) { + return $result[$name]; + } +} + +/** + * Implements 'load multiple callback' for myobj exportables. + */ +function mymodule_myobj_load_multiple(array $names) { + ctools_include('export') + $results = ctools_export_load_object('mymodule_myobjs', 'names', $names); + return array_filter($results); +} ++ +The save callback
+Calling ctools_export_crud_save($table, $object) will invoke your save callback. The default handlers should be sufficient for most uses. For the default save mechanism to work, you must define a 'primary key' in the 'export' section of your schema. The following example duplicates the default functionality for the myobj. + ++/** +* Save a single myobj. +*/ +function mymodule_myobj_save(&$myobj) { + $update = (isset($myobj->oid) && is_numeric($myobj->oid)) ? array('oid') : array(); + return drupal_write_record('myobj', $myobj, $update); +} ++ +Default hooks for your exports
+All exportables come with a 'default' hook, which can be used to put your exportable into code. The easiest way to actually use this hook is to set up your exportable for bulk exporting, enable the bulk export module and export an object.
diff --git a/sites/all/modules/contrib/ctools/help/form.html b/sites/all/modules/contrib/ctools/help/form.html new file mode 100644 index 0000000..b9b6d9c --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/form.html @@ -0,0 +1 @@ +To be written.
diff --git a/sites/all/modules/contrib/ctools/help/modal.html b/sites/all/modules/contrib/ctools/help/modal.html new file mode 100644 index 0000000..ea823a0 --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/modal.html @@ -0,0 +1,215 @@ +CTools provides a simple modal that can be used as a popup to place forms. It differs from the normal modal frameworks in that it does not do its work via an iframe. This is both an advantage and a disadvantage. The iframe simply renders normal pages in a sub-browser and they can do their thing. That makes it much easier to put arbitrary pages and forms in a modal. However, the iframe is not very good at actually communicating changes to the main page, so you cannot open the modal, have it do some work, and then modify the page.
+ +Invoking the modal
+ +The basic form of the modal can be set up just by including the javascript and adding the proper class to a link or form that will open the modal. To include the proper javascript, simply include the library and call the add_js function:
+ +ctools_include('modal'); +ctools_modal_add_js(); ++ +You can have links and buttons bound to use the modal by adding the class ctools-use-modal. For convenience, there is a helper function to try and do this, though it's not great at doing all links so using this is optional:
+ +/** + * Render an image as a button link. This will automatically apply an AJAX class + * to the link and add the appropriate javascript to make this happen. + * + * @param $image + * The path to an image to use that will be sent to theme('image') for rendering. + * @param $dest + * The destination of the link. + * @param $alt + * The alt text of the link. + * @param $class + * Any class to apply to the link. @todo this should be a options array. + */ +function ctools_modal_image_button($image, $dest, $alt, $class = '') { + return ctools_ajax_text_button(theme('image', array('path' => $image), $dest, $alt, $class, 'ctools-use-modal'); +} + +/** + * Render text as a link. This will automatically apply an AJAX class + * to the link and add the appropriate javascript to make this happen. + * + * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will + * not use user input so this is a very minor concern. + * + * @param $text + * The text to display as the link. + * @param $dest + * The destination of the link. + * @param $alt + * The alt text of the link. + * @param $class + * Any class to apply to the link. @todo this should be a options array. + */ +function ctools_modal_text_button($text, $dest, $alt, $class = '') { + return ctools_ajax_text_button($text, $dest, $alt, $class, 'ctools-use-modal'); +} ++ +Like with all CTools' AJAX functionality, the href of the link will be the destination, with any appearance of /nojs/ converted to /ajax/.
+ +For submit buttons, however, the URL may be found a different, slightly more complex way. If you do not wish to simply submit the form to the modal, you can create a URL using hidden form fields. The ID of the item is taken and -url is appended to it to derive a class name. Then, all form elements that contain that class name are founded and their values put together to form a URL.
+ +For example, let's say you have an 'add' button, and it has a select form item that tells your system what widget it is adding. If the id of the add button is edit-add, you would place a hidden input with the base of your URL in the form and give it a class of 'edit-add-url'. You would then add 'edit-add-url' as a class to the select widget allowing you to convert this value to the form without posting. If no URL is found, the action of the form will be used and the entire form posted to it.
+ +Customizing the modal
+ +If you do not wish to use the default modal, the modal can be customized by creating an array of data to define a customized modal. To do this, you add an array of info to the javascript settings to define the customizations for the modal and add an additional class to your modal link or button to tell it which modal to use.
+ +First, you need to create a settings array. You can do this most easily with a bit of PHP:
+ +drupal_add_js(array( + 'my-modal-style' => array( + 'modalSize' => array( + 'type' => 'fixed', + 'width' => 250, + 'height' => 250, + ), + ), + ), 'setting'); ++ +The key to the array above (in this case, my-modal-style) is the identifier to your modal theme. You can have multiple modal themes on a page, so be sure to use an ID that will not collide with some other module's use. Using your module or theme as a prefix is a good idea.
+ +Then, when adding the ctools-use-modal class to your link or button, also add the following class: ctools-modal-ID (in the example case, that would be ctools-modal-my-modal-style).
+ +modalSize can be 'fixed' or 'scale'. If fixed it will be a raw pixel value; if 'scale' it will be a percentage of the screen.
+ +You can set:
++
+ +- modalSize: an array of data to control the sizing of the modal. It can contain: +
++
+- type: Either fixed or scale. If fixed, the modal will always be a fixed size. If scale the modal will scale to a percentage of the browser window. Default: scale. +
- width: If fixed the width in pixels. If scale the percentage of the screen expressed as a number less than zero. (For 80 percent, use .8, for example). Default: .8
+- height: If fixed the height in pixels. If scale the percentage of the screen expressed as a number less than zero. (For 80 percent, use .8, for example). Default: .8
+- addWidth: Any additional width to add to the modal in pixels. Only useful if the type is scale. Default: 0
+- addHeight: Any additional height to add to the modal in pixels. Only useful if the type is scale. Default: 0
+- contentRight: The number of pixels to remove from the content inside the modal to make room for scroll bar and decorations. Default: 25
+- contentBottom: The number of pixels to remove from the content inside the modal to make room for scroll bar and decorations. Default: 45
+- modalTheme: The Drupal javascript themable function which controls how the modal will be rendered. This function must be in the Drupal.theme.prototype namespace. If you set this value, you must include a corresponding function in a javascript file and use drupal_add_js() to add that file. Default: CToolsModalDialog +
++ Drupal.theme.prototype.CToolsModalDialog = function () { + var html = '' + html += ' <div id="ctools-modal">' + html += ' <div class="ctools-modal-content">' // panels-modal-content + html += ' <div class="modal-header">'; + html += ' <a class="close" href="#">'; + html += Drupal.CTools.Modal.currentSettings.closeText + Drupal.CTools.Modal.currentSettings.closeImage; + html += ' </a>'; + html += ' <span id="modal-title" class="modal-title"> </span>'; + html += ' </div>'; + html += ' <div id="modal-content" class="modal-content">'; + html += ' </div>'; + html += ' </div>'; + html += ' </div>'; + + return html; + } +- throbberTheme: The Drupal javascript themable function which controls how the modal throbber will be rendered. This function must be in the Drupal.theme.prototype namespace. If you set this value, you must include a corresponding function in a javascript file and use drupal_add_js() to add that file. Default: CToolsModalThrobber +
++ Drupal.theme.prototype.CToolsModalThrobber = function () { + var html = ''; + html += ' <div id="modal-throbber">'; + html += ' <div class="modal-throbber-wrapper">'; + html += Drupal.CTools.Modal.currentSettings.throbber; + html += ' </div>'; + html += ' </div>'; + + return html; + }; ++- modalOptions: The options object that's sent to Drupal.CTools.Modal.modalContent. Can contain any CSS settings that will be applied to the modal backdrop, in particular settings such as opacity and background. Default: array('opacity' => .55, 'background' => '#fff');
+- animation: Controls how the modal is animated when it is first drawn. Either show, fadeIn or slideDown. Default: show
+- animationSpeed: The speed of the animation, expressed either as a word jQuery understands (slow, medium or fast) or a number of milliseconds for the animation to run. Defaults: fast
+- closeText: The text to display for the close button. Be sure to wrap this in t() for translatability. Default: t('Close window')
+- closeImage: The image to use for the close button of the modal. Default: theme('image', array('path' => ctools_image_path('icon-close-window.png'), 'alt' => t('Close window'), 'title' => t('Close window')))
+- loadingText: The text to display for the modal title during loading, along with the throbber. Be sure to wrap this in t() for translatability. Default: t('Close window')
+- throbber: The HTML to display for the throbber image. You can use this instead of CToolsModalThrobber theme if you just want to change the image but not the throbber HTML. Default: theme('image', array('path' => ctools_image_path('throbber.gif'), 'alt' => t('Loading...'), 'title' => t('Loading')))
+Rendering within the modal
+To render your data inside the modal, you need to provide a page callback in your module that responds more or less like a normal page.
+ +In order to handle degradability, it's nice to allow your page to work both inside and outside of the modal so that users whose javascript is turned off can still use your page. There are two ways to accomplish this. First, you can embed 'nojs' as a portion of the URL and then watch to see if that turns into 'ajax' when the link is clicked. Second, if you do not wish to modify the URLs, you can check $_POST['js'] or $_POST['ctools_js'] to see if that flag was set. The URL method is the most flexible because it is easy to send the two links along completely different paths if necessary, and it is also much easier to manually test your module's output by manually visiting the nojs URL. It's actually quite difficult to do this if you have to set $_POST['js'] to test.
+ +In your hook_menu, you can use the a CTools' AJAX convenience loader to help:
+ ++ $items['ctools_ajax_sample/%ctools_js/login'] = array( + 'title' => 'Login', + 'page callback' => 'ctools_ajax_sample_login', + 'page arguments' => array(1), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); ++ +The first argument to the page callback will be the result of ctools_js_load() which will return TRUE if 'ajax' was the string, and FALSE if anything else (i.e, nojs) is the string. Which means you can then declare your function like this:
+ ++function ctools_ajax_sample_login($js) { + if ($js) { + // react with the modal + } + else { + // react without the modal + } +} ++ +If your modal does not include a form, rendering the output you wish to give the user is just a matter of calling the modal renderer with your data:
+ ++function ctools_ajax_hello_world($js) { + $title = t('Greetings'); + $output = '<p>' . t('Hello world') . ''</p>'; + if ($js) { + ctools_modal_render($title, $output); + } + else { + drupal_set_title($title); + return $output; + } +} ++ +If you need to do more than just render your modal, you can use a CTools $commands array. See the function ctools_modal_render() for more information on what you need to do here.
+ +If you are displaying a form -- and the vast majority of modals display forms -- then you need to do just slightly more. Fortunately there is the ctools_modal_form_wrapper() function:
+ ++ ctools_include('modal'); + ctools_include('ajax'); + $form_state = array( + 'title' => t('Title of my form'), + 'ajax' => $js, + ); + $output = ctools_modal_form_wrapper('my_form', $form_state); + // There are some possible states after calling the form wrapper: + // 1) We are not using $js and the form has been executed. + // 2) We are using $js and the form was successfully submitted and + // we need to dismiss the modal. + // Most other states are handled automatically unless you set flags in + // $form_state to avoid handling them, so we only deal with those two + // states. + if ($form_state['executed'] && $js) { + $commands = array(); + $commands[] = ctools_modal_command_dismiss(t('Login Success')); + // In typical usage you will do something else here, such as update a + // div with HTML or redirect the page based upon the results of the modal + // form. + print ajax_render($commands); + exit; + } + + // Otherwise, just return the output. + return $output; ++ +You can also use CTools' form wizard inside the modal. You do not need to do much special beyond setting $form_state['modal'] = TRUE in the wizard form; it already knows how to handle the rest.
diff --git a/sites/all/modules/contrib/ctools/help/object-cache.html b/sites/all/modules/contrib/ctools/help/object-cache.html new file mode 100644 index 0000000..801a836 --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/object-cache.html @@ -0,0 +1,132 @@ +The CTools Object Cache is a specialized cache for storing data that is non-volatile. This differs from the standard Drupal cache mechanism, which is volatile, meaning that the data can be cleared at any time and it is expected that any cached data can easily be reconstructed. In contrast, data stored in this cache is not expected to be reconstructable. It is primarily used for storing user input which is retrieved in stages, allowing for more complex user interface interactions.
+ +The object cache consists of 3 normal functions for cache maintenance, and 2 additional functions to facilitate locking.
+ +To use any of these functions, you must first use ctools_include:
+ ++ctools_include('object-cache'); ++ ++/** + * Get an object from the non-volatile ctools cache. + * + * This function caches in memory as well, so that multiple calls to this + * will not result in multiple database reads. + * + * @param $obj + * A 128 character or less string to define what kind of object is being + * stored; primarily this is used to prevent collisions. + * @param $name + * The name of the object being stored. + * @param $skip_cache + * Skip the memory cache, meaning this must be read from the db again. + * + * @return + * The data that was cached. + */ +function ctools_object_cache_get($obj, $name, $skip_cache = FALSE) { ++ ++/** + * Store an object in the non-volatile ctools cache. + * + * @param $obj + * A 128 character or less string to define what kind of object is being + * stored; primarily this is used to prevent collisions. + * @param $name + * The name of the object being stored. + * @param $cache + * The object to be cached. This will be serialized prior to writing. + */ +function ctools_object_cache_set($obj, $name, $cache) { ++ ++/** + * Remove an object from the non-volatile ctools cache + * + * @param $obj + * A 128 character or less string to define what kind of object is being + * stored; primarily this is used to prevent collisions. + * @param $name + * The name of the object being removed. + */ +function ctools_object_cache_clear($obj, $name) { ++ +To facilitate locking, which is the ability to prohibit updates by other users while one user has an object cached, this system provides two functions:
+ ++/** + * Determine if another user has a given object cached. + * + * This is very useful for 'locking' objects so that only one user can + * modify them. + * + * @param $obj + * A 128 character or less string to define what kind of object is being + * stored; primarily this is used to prevent collisions. + * @param $name + * The name of the object being removed. + * + * @return + * An object containing the UID and updated date if found; NULL if not. + */ +function ctools_object_cache_test($obj, $name) { ++ +The object returned by ctools_object_cache_test can be directly used to determine whether a user should be allowed to cache their own version of an object.
+ +To allow the concept of breaking a lock, that is, clearing another users changes:
+ ++/** + * Remove an object from the non-volatile ctools cache for all session IDs. + * + * This is useful for clearing a lock. + * + * @param $obj + * A 128 character or less string to define what kind of object is being + * stored; primarily this is used to prevent collisions. + * @param $name + * The name of the object being removed. + */ +function ctools_object_cache_clear_all($obj, $name) { ++ +Typical best practice is to use wrapper functions such as these:
+ ++/** + * Get the cached changes to a given task handler. + */ +function delegator_page_get_page_cache($name) { + ctools_include('object-cache'); + $cache = ctools_object_cache_get('delegator_page', $name); + if (!$cache) { + $cache = delegator_page_load($name); + $cache->locked = ctools_object_cache_test('delegator_page', $name); + } + + return $cache; +} + +/** + * Store changes to a task handler in the object cache. + */ +function delegator_page_set_page_cache($name, $page) { + ctools_include('object-cache'); + $cache = ctools_object_cache_set('delegator_page', $name, $page); +} + +/** + * Remove an item from the object cache. + */ +function delegator_page_clear_page_cache($name) { + ctools_include('object-cache'); + ctools_object_cache_clear('delegator_page', $name); +} +diff --git a/sites/all/modules/contrib/ctools/help/plugins-api.html b/sites/all/modules/contrib/ctools/help/plugins-api.html new file mode 100644 index 0000000..548f17b --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/plugins-api.html @@ -0,0 +1,55 @@ +APIs are a form of plugins that are tightly associated with a module. Instead of a module providing any number of plugins, each module provides only one file for an API and this file can contain hooks that the module should invoke.
+ +Modules support this API by implementing hook_ctools_plugin_api($module, $api). If they support the API, they return a packet of data:
+ ++function mymodule_ctools_plugin_api($module, $api) { + if ($module == 'some module' && $api = 'some api') { + return array( + 'version' => The minimum API version this system supports. If this API version is incompatible then the .inc file will not be loaded. + 'path' => Where to find the file. Optional; if not specified it will be the module's directory. + 'file' => an alternative version of the filename. If not specified it will be $module.$api.inc + ); + } +} ++ +This implementation must be in the .module file.
+ +Modules utilizing this can invole ctools_plugin_api_include() in order to ensure all modules that support the API will have their files loaded as necessary. It's usually easiest to create a small helper function like this:
+ ++define('MYMODULE_MINIMUM_VERSION', 1); +define('MYMODULE_VERSION', 1); + +function mymodule_include_api() { + ctools_include('plugins'); + return ctools_plugin_api_include('mymodule', 'myapi', MYMODULE_MINIMUM_VERSION, MYMODULE_VERSION); +} ++ +Using a define will ensure your use of version numbers is consistent and easy to update when you make API changes. You can then use the usual module_invoke type commands:
+ ++mymodule_include_api(); +module_invoke('myhook', $data); ++ +If you need to pass references, this construct is standard:
+ ++foreach (mymodule_include_api() as $module => $info) { + $function = $module . '_hookname'; + // Just because they implement the API and include a file does not guarantee they implemented + // a hook function! + if (!function_exists($function)) { + continue; + } + + // Typically array_merge() is used below if data is returned. + $result = $function($data1, $data2, $data3); +} ++ +TODO: There needs to be a way to check API version without including anything, as a module may simply +provide normal plugins and versioning could still matter.
diff --git a/sites/all/modules/contrib/ctools/help/plugins-creating.html b/sites/all/modules/contrib/ctools/help/plugins-creating.html new file mode 100644 index 0000000..2323705 --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/plugins-creating.html @@ -0,0 +1,203 @@ +There are two primary pieces to using plugins. The first is getting the data, and the second is using the data. + +Defining a plugin
+To define that you offer a plugin that modules can implement, you first must implement hook_ctools_plugin_type() to tell the plugin system about your plugin. + ++/** + * Implements hook_ctools_plugin_type() to inform CTools about the layout plugin. + */ +function panels_ctools_plugin_type() { + $plugins['layouts'] = array( + 'load themes' => TRUE, + ); + + return $plugins; +} ++ +The following information can be specified for each plugin type: ++
+ +In addition, there is a 'module' and 'type' settings; these are for internal use of the plugin system and you should not change these. +- cache
+- Defaults to: FALSE
+- If set to TRUE, the results of ctools_get_plugins will be cached in the 'cache' table (by default), thus preventing .inc files from being loaded. ctools_get_plugins looking for a specific plugin will always load the appropriate .inc file.
+- cache table
+- Defaults to: 'cache'
+- If 'cache' is TRUE, then this value specifies the cache table where the cached plugin information will be stored.
+- classes
+- Defaults to: array()
+- An array of class identifiers(i.e. plugin array keys) which a plugin of this type uses to provide classes to the CTools autoloader. For example, if classes is set to array('class'), then CTools will search each $plugin['class'] for a class to autoload. Depending of the plugin structure, a class identifier may be either:
++
+- - a file name:
+- the file which holds the class with the name structure as: [filename].[class].php
+- in this case the class name can be different than the class identifier
+- - the class name:
+- if the class is in the same file as the $plugin
+- the plugin .inc file can have a different name than the class identifier
+- defaults
+- Defaults to: array()
+- An array of defaults that should be added to each plugin; this can be used to ensure that every plugin has the basic data necessary. These defaults will not ovewrite data supplied by the plugin. This could also be a function name, in which case the callback will be used to provide defaults. NOTE, however, that the callback-based approach is deprecated as it is redundant with the 'process' callback, and as such will be removed in later versions. Consequently, you should only use the array form for maximum cross-version compatibility.
+- load themes
+- Defaults to: FALSE
+- If set to TRUE, then plugins of this type can be supplied by themes as well as modules. If this is the case, all themes that are currently enabled will provide a plugin: NOTE: Due to a slight UI bug in Drupal, it is possible for the default theme to be active but not enabled. If this is the case, that theme will NOT provide plugins, so if you are using this feature, be sure to document that issue. Also, themes set via $custom_theme do not necessarily need to be enabled, but the system has no way of knowing what those themes are, so the enabled flag is the only true method of identifying which themes can provide layouts.
+- hook
+- Defaults to: (dynamic value)
+- The name of the hook used to collect data for this plugin. Normally this is $module . '_' . $type -- but this can be changed here. If you change this, you MUST be sure to document this for your plugin implementors as it will change the format of the specially named hook. +
- process
+- Defaults to: ''
+- An optional function callback to use for processing a plugin. This can be used to provide automated settings that must be calculated per-plugin instance (i.e., it is not enough to simply append an array via 'defaults'). The parameters on this callback are: callback(&$plugin, $info) where $plugin is a reference to the plugin as processed and $info is the fully processed result of hook_ctools_plugin_api_info(). +
- extension
+- Defaults to: 'inc'
+- Can be used to change the extension on files containing plugins of this type. By default the extension will be "inc", though it will default to "info" if "info files" is set to true. Do not include the dot in the extension if changing it, that will be added automatically.
+- info file
+- Defaults to: FALSE
+- If set to TRUE, then the plugin will look for a .info file instead of a .inc. Internally, this will look exactly the same, though obviously a .info file cannot contain functions. This can be good for styles that may not need to contain code.
+- use hooks
+- Defaults to: TRUE*
+- Use to enable support for plugin definition hooks instead of plugin definition files. NOTE: using a central plugin definition hook is less optimal for the plugins system, and as such this will default to FALSE in later versions.
+- child plugins
+- Defaults to: FALSE
+- If set to TRUE, the plugin type can automatically have 'child plugins' meaning each plugin can actually provide multiple plugins. This is mostly used for plugins that store some of their information in the database, such as views, blocks or exportable custom versions of plugins.
+- To implement, each plugin can have a 'get child' and 'get children' callback. Both of these should be implemented for performance reasons, since it is best to avoid getting all children if necessary, but if 'get child' is not implemented, it will fall back to 'get children' if it has to.
+- Child plugins should be named parent:child, with the : being the separator, so that it knows which parent plugin to ask for the child. The 'get children' method should at least return the parent plugin as part of the list, unless it wants the parent plugin itself to not be a choosable option, which is not unheard of.
+- 'get children' arguments are ($plugin, $parent) and 'get child' arguments are ($plugin, $parent, $child). +
Getting the data
+To create a plugin, a module only has to execute ctools_get_plugins with the right data: + ++ ctools_include('plugins'); + ctools_get_plugins($module, $type, [$id]) ++ +In the above example, $module should be your module's name and $type is the type of the plugin. It is typically best practice to provide some kind of wrapper function to make this easier. For example, Panels provides the following functions to implement the 'content_types' plugin: + ++/** + * Fetch metadata on a specific content_type plugin. + * + * @param $content type + * Name of a panel content type. + * + * @return + * An array with information about the requested panel content type. + */ +function panels_get_content_type($content_type) { + ctools_include('context'); + ctools_include('plugins'); + return ctools_get_plugins('panels', 'content_types', $content_type); +} + +/** + * Fetch metadata for all content_type plugins. + * + * @return + * An array of arrays with information about all available panel content types. + */ +function panels_get_content_types() { + ctools_include('context'); + ctools_include('plugins'); + return ctools_get_plugins('panels', 'content_types'); +} ++ +Using the data
+ +Each plugin returns a packet of data, which is added to with a few defaults. Each plugin is guaranteed to always have the following data: ++
+ +- name
+- The name of the plugin. This is also the key in the array, of the full list of plugins, and is placed here since that is not always available.
+- module
+- The module that supplied the plugin.
+- file
+- The actual file containing the plugin.
+- path
+- The path to the file containing the plugin. This is useful for using secondary files, such as templates, css files, images, etc, that may come with a plugin.
+Any of the above items can be overridden by the plugin itself, though the most likely one to be modified is the 'path'.
+ +The most likely data (beyond simple printable data) for a plugin to provide is a callback. The plugin system provides a pair of functions to make it easy and consistent for these callbacks to be used. The first is ctools_plugin_get_function, which requires the full $plugin object.
+ ++/** + * Get a function from a plugin, if it exists. If the plugin is not already + * loaded, try ctools_plugin_load_function() instead. + * + * @param $plugin + * The loaded plugin type. + * @param $callback_name + * The identifier of the function. For example, 'settings form'. + * + * @return + * The actual name of the function to call, or NULL if the function + * does not exist. + */ +function ctools_plugin_get_function($plugin, $callback_name) ++ +The second does not require the full $plugin object, and will load it:
+ ++/** + * Load a plugin and get a function name from it, returning success only + * if the function exists. + * + * @param $module + * The module that owns the plugin type. + * @param $type + * The type of plugin. + * @param $id + * The id of the specific plugin to load. + * @param $callback_name + * The identifier of the function. For example, 'settings form'. + * + * @return + * The actual name of the function to call, or NULL if the function + * does not exist. + */ +function ctools_plugin_load_function($module, $type, $id, $callback_name) { ++ +Both of these functions will ensure any needed files are included. In fact, it allows each callback to specify alternative include files. The plugin implementation could include code like this:
+ ++ 'callback_name' => 'actual_name_of_function_here', ++ +Or like this:
++ 'callback_name' => array( + 'file' => 'filename', + 'path' => 'filepath', // optional, will use plugin path if absent + 'function' => 'actual_name_of_function_here', + ), ++ +An example, for 'plugin_example' type
+ ++$plugin = array( + 'name' => 'my_plugin', + 'module' => 'my_module', + 'example_callback' => array( + 'file' => 'my_plugin.extrafile.inc', + 'function' => 'my_module_my_plugin_example_callback', + ), +); ++ +To utilize this callback on this plugin:
+ ++if ($function = ctools_plugin_get_function($plugin, 'example_callback')) { + $function($arg1, $arg2, $etc); +} ++ +Document your plugins!
+ +Since the data provided by your plugin tends to be specific to your plugin type, you really need to document what the data returned in the hook in the .inc file will be or nobody will figure it out. Use advanced help and document it there. If every system that utilizes plugins does this, then plugin implementors will quickly learn to expect the documentation to be in the advanced help.
diff --git a/sites/all/modules/contrib/ctools/help/plugins-implementing.html b/sites/all/modules/contrib/ctools/help/plugins-implementing.html new file mode 100644 index 0000000..c95e72d --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/plugins-implementing.html @@ -0,0 +1,62 @@ +There are two parts to implementing a plugin: telling the system where it is, and implementing one or more .inc files that contain the plugin data.
+ +Telling the system where your plugins live
+How a module implements plugins
+To implement any plugins at all, you must implement a single function for all plugins: hook_ctools_plugin_directory. Every time a module loads plugins, this hook will be called to see which modules implement those plugins and in what directory those plugins will live.
+ ++function hook_ctools_plugin_directory($module, $plugin) { + if ($module == 'panels' && $plugin == 'content_types') { + return 'plugins/content_types'; + } +} ++ +The directory returned should be relative to your module. Another common usage is to simply return that you implement all plugins owned by a given module (or modules):
+ ++function hook_ctools_plugin_directory($module, $plugin) { + if ($module == 'panels') { + return 'plugins/' . $plugin; + } +} ++ +Typically, it is recommended that all plugins be placed into the 'plugins' directory for clarity and maintainability. Inside the directory, any number of subdirectories can be used. For plugins that require extra files, such as templates, css, javascript or image files, this is highly recommended:
++mymodule.module +mymodule.info +plugins/ + content_types/ + my_content_type.inc + layouts/ + my_layout.inc + my_layout.css + my_layout.tpl.php + my_layout_image.png ++ +How a theme implements plugins
+Themes can implement plugins if the plugin owner specified that it's possible in its hook_ctools_plugin_type() call. If so, it is generally exactly the same as modules, except for one important difference: themes don't get hook_ctools_plugin_directory(). Instead, themes add a line to their .info file:
+ ++plugins[module][type] = directory ++ +How to structure the .inc file
+ +The top of the .inc file should contain an array that defines the plugin. This array is simply defined in the global namespace of the file and does not need a function. Note that previous versions of this plugin system required a specially named function. While this function will still work, its use is now discouraged, as it is annoying to name properly.
+ +This array should look something like this:
+ ++$plugin = array( + 'key' => 'value', +); ++ +Several values will be filled in for you automatically, but you can override them if necessary. They include 'name', 'path', 'file' and 'module'. Additionally, the plugin owner can provide other defaults as well.
+ +There are no required keys by the plugin system itself. The only requirements in the $plugin array will be defined by the plugin type.
+ +After this array, if your plugin needs functions, they can be declared. Different plugin types have different needs here, so exactly what else will be needed will change from type to type.
diff --git a/sites/all/modules/contrib/ctools/help/plugins.html b/sites/all/modules/contrib/ctools/help/plugins.html new file mode 100644 index 0000000..1e64da0 --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/plugins.html @@ -0,0 +1,5 @@ +The plugins tool allows a module to allow other modules (and themes!) to provide plugins which provide some kind of functionality or some kind of task. For example, in Panels there are several types of plugins: Content types (which are like blocks), layouts (which are page layouts) and styles (which can be used to style a panel). Each plugin is represented by a .inc file, and the functionality they offer can differ wildly.
+ +A module which uses plugins can implement a hook describing the plugin (which is not necessary, as defaults will be filled in) and then calls a ctools function which loads either all the known plugins (used for listing/choosing) or a specific plugin (used when it's known which plugin is needed). From the perspective of the plugin system, a plugin is a packet of data, usually some printable info and a list of callbacks. It is up to the module implementing plugins to determine what that info means and what the callbacks do.
+ +A module which implements plugins must first implement the hook_ctools_plugin_directory function, which simply tells the system which plugins are supported and what directory to look in. Each plugin will then be in a separate .inc file in the directory supplied. The .inc file will contain a specially named hook which returns the data necessary to implement the plugin.
diff --git a/sites/all/modules/contrib/ctools/help/wizard.html b/sites/all/modules/contrib/ctools/help/wizard.html new file mode 100644 index 0000000..33fc456 --- /dev/null +++ b/sites/all/modules/contrib/ctools/help/wizard.html @@ -0,0 +1,311 @@ +Form wizards, or multi-step forms, are a process by which the user goes through or can use an arbitrary number of different forms to create a single object or perform a single task. Traditionally the multi-step form is difficult in Drupal because there is no easy place to put data in between forms. No longer! The form wizard tool allows a single entry point to easily set up a wizard of multiple forms, provide callbacks to handle data storage and updates between forms and when forms are completed. This tool pairs well with the object cache tool for storage.
+ +The form info array
+The wizard starts with an array of data that describes all of the forms available to the wizard and sets options for how the wizard will present and control the flow. Here is an example of the $form_info array as used in the delegator module:
+ ++ $form_info = array( + 'id' => 'delegator_page', + 'path' => "admin/structure/pages/edit/$page_name/%step", + 'show trail' => TRUE, + 'show back' => TRUE, + 'show return' => FALSE, + 'next callback' => 'delegator_page_add_subtask_next', + 'finish callback' => 'delegator_page_add_subtask_finish', + 'return callback' => 'delegator_page_add_subtask_finish', + 'cancel callback' => 'delegator_page_add_subtask_cancel', + 'order' => array( + 'basic' => t('Basic settings'), + 'argument' => t('Argument settings'), + 'access' => t('Access control'), + 'menu' => t('Menu settings'), + 'multiple' => t('Task handlers'), + ), + 'forms' => array( + 'basic' => array( + 'form id' => 'delegator_page_form_basic' + ), + 'access' => array( + 'form id' => 'delegator_page_form_access' + ), + 'menu' => array( + 'form id' => 'delegator_page_form_menu' + ), + 'argument' => array( + 'form id' => 'delegator_page_form_argument' + ), + 'multiple' => array( + 'form id' => 'delegator_page_argument_form_multiple' + ), + ), + ); ++ +The above array starts with an id which is used to identify the wizard in various places and a path which is needed to redirect to the next step between forms. It then creates some settings which control which pieces are displayed. In this case, it displays a form trail and a 'back' button, but not the 'return' button. Then there are the wizard callbacks which allow the wizard to act appropriately when forms are submitted. Finally it contains a list of forms and their order so that it knows which forms to use and what order to use them by default. Note that the keys in the order and list of forms match; that key is called the step and is used to identify each individual step of the wizard.
+ +Here is a full list of every item that can be in the form info array:
+ ++
+ +- id
+- An id for wizard. This is used like a hook to automatically name callbacks, as well as a form step's form building function. It is also used in trail theming.
+ +- path
+- The path to use when redirecting between forms. %step will be replaced with the key for the form.
+ +- return path
+- When a form is complete, this is the path to go to. This is required if the 'return' button is shown and not using AJAX. It is also used for the 'Finish' button. If it is not present and needed, the cancel path will also be checked.
+ +- cancel path
+- When a form is canceled, this is the path to go to. This is required if the 'cancel' is shown and not using AJAX.
+ +- show trail
+- If set to TRUE, the form trail will be shown like a breadcrumb at the top of each form. Defaults to FALSE.
+ +- show back
+- If set to TRUE, show a back button on each form. Defaults to FALSE.
+ +- show return
+- If set to TRUE, show a return button. Defaults to FALSE.
+ +- show cancel
+- If set to TRUE, show a cancel button. Defaults to FALSE.
+ +- back text
+- Set the text of the 'back' button. Defaults to t('Back').
+ +- next text
+- Set the text of the 'next' button. Defaults to t('Continue').
+ +- return text
+- Set the text of the 'return' button. Defaults to t('Update and return').
+ +- finish text
+- Set the text of the 'finish' button. Defaults to t('Finish').
+ +- cancel text
+- Set the text of the 'cancel' button. Defaults to t('Cancel').
+ +- ajax
+- Turn on AJAX capabilities, using CTools' ajax.inc. Defaults to FALSE.
+ +- modal
+- Put the wizard in the modal tool. The modal must already be open and called from an ajax button for this to work, which is easily accomplished using functions provided by the modal tool.
+ +- ajax render
+- A callback to display the rendered form via ajax. This is not required if using the modal tool, but is required otherwise since ajax by itself does not know how to render the results. Params: &$form_state, $output.
+ +- finish callback
+- The function to call when a form is complete and the finish button has been clicked. This function should finalize all data. Params: &$form_state. +Defaults to $form_info['id']._finish if function exists. +
+ +- cancel callback
+- The function to call when a form is canceled by the user. This function should clean up any data that is cached. Params: &$form_state. +Defaults to $form_info['id']._cancel if function exists.
+ +- return callback
+- The function to call when a form is complete and the return button has been clicked. This is often the same as the finish callback. Params: &$form_state. +Defaults to $form_info['id']._return if function exists.
+ +- next callback
+- The function to call when the next button has been clicked. This function should take the submitted data and cache it for later use by the finish callback. Params: &$form_state. +Defaults to $form_info['id']._next if function exists.
+ +- order
+- An optional array of forms, keyed by the step, which represents the default order the forms will be displayed in. If not set, the forms array will control the order. Note that submit callbacks can override the order so that branching logic can be used.
+ +- forms
+- An array of form info arrays, keyed by step, describing every form available to the wizard. If order array isn't set, the wizard will use this to set the default order. Each array contains: +
++
+- form id
+- + The id of the form, as used in the Drupal form system. This is also the name of the function that represents the form builder. + Defaults to $form_info['id']._.$step._form. +
+ +- include
+- The name of a file to include which contains the code for this form. This makes it easy to include the form wizard in another file or set of files. This must be the full path of the file, so be sure to use drupal_get_path() when setting this. This can also be an array of files if multiple files need to be included.
+ +- title
+- The title of the form, to be optionally set via drupal_get_title. This is required when using the modal if $form_state['title'] is not set.
+Invoking the form wizard
+Your module should create a page callback via hook_menu, and this callback should contain an argument for the step. The path that leads to this page callback should be the same as the 'path' set in the $form_info array.
+ +The page callback should set up the $form_info, and figure out what the default step should be if no step is provided (note that the wizard does not do this for you; you MUST specify a step). Then invoke the form wizard:
+ ++ $form_state = array(); + ctools_include('wizard'); + $output = ctools_wizard_multistep_form($form_info, $step, $form_state); ++ +If using AJAX or the modal, This part is actually done! If not, you have one more small step:
+ ++ return $output; ++ +Forms and their callbacks
+Each form within the wizard is a complete, independent form using Drupal's Form API system. It has a form builder callback as well as submit and validate callbacks and can be form altered. The primary difference between these forms and a normal Drupal form is that the submit handler should not save any data. Instead, it should make any changes to a cached object (usually placed on the $form_state) and only the _finish or _return handler should actually save any real data.
+ +How you handle this is completely up to you. The recommended best practice is to use the CTools Object cache, and a good way to do this is to write a couple of wrapper functions around the cache that look like these example functions:
+ ++/** + * Get the cached changes to a given task handler. + */ +function delegator_page_get_page_cache($name) { + ctools_include('object-cache'); + $cache = ctools_object_cache_get('delegator_page', $name); + if (!$cache) { + $cache = delegator_page_load($name); + $cache->locked = ctools_object_cache_test('delegator_page', $name); + } + + return $cache; +} + +/** + * Store changes to a task handler in the object cache. + */ +function delegator_page_set_page_cache($name, $page) { + ctools_include('object-cache'); + $cache = ctools_object_cache_set('delegator_page', $name, $page); +} + +/** + * Remove an item from the object cache. + */ +function delegator_page_clear_page_cache($name) { + ctools_include('object-cache'); + ctools_object_cache_clear('delegator_page', $name); +} ++ +Using these wrappers, when performing a get_cache operation, it defaults to loading the real object. It then checks to see if another user has this object cached using the ctools_object_cache_test() function, which automatically sets a lock (which can be used to prevent writes later on).
+ +With this set up, the _next, _finish and _cancel callbacks are quite simple:
+ ++/** + * Callback generated when the add page process is finished. + */ +function delegator_page_add_subtask_finish(&$form_state) { + $page = &$form_state['page']; + + // Create a real object from the cache + delegator_page_save($page); + + // Clear the cache + delegator_page_clear_page_cache($form_state['cache name']); +} + +/** + * Callback generated when the 'next' button is clicked. + * + * All we do here is store the cache. + */ +function delegator_page_add_subtask_next(&$form_state) { + // Update the cache with changes. + delegator_page_set_page_cache($form_state['cache name'], $form_state['page']); +} + +/** + * Callback generated when the 'cancel' button is clicked. + * + * All we do here is clear the cache. + */ +function delegator_page_add_subtask_cancel(&$form_state) { + // Update the cache with changes. + delegator_page_clear_page_cache($form_state['cache name']); +} ++ +All that's needed to tie this together is to understand how the changes made it into the cache in the first place. This happened in the various form _submit handlers, which made changes to $form_state['page'] based upon the values set in the form:
+ ++/** + * Store the values from the basic settings form. + */ +function delegator_page_form_basic_submit($form, &$form_state) { + if (!isset($form_state['page']->pid) && empty($form_state['page']->import)) { + $form_state['page']->name = $form_state['values']['name']; + } + + $form_state['page']->admin_title = $form_state['values']['admin_title']; + $form_state['page']->path = $form_state['values']['path']; + + return $form; +} ++ +No database operations were made during this _submit, and that's a very important distinction about this system.
+ +Proper handling of back button
+When using 'show back' => TRUE the cached data should be assigned to the #default_value form property. Otherwise when the user goes back to the previous step the forms default values instead of his (cached) input is used.
+ ++/** + * Form builder function for wizard. + */ +function wizardid_step2_form($form, &$form_state) { + $form_state['my data'] = my_module_get_cache($form_state['cache name']); + $form['example'] = array( + '#type' => 'radios', + '#title' => t('Title'), + '#default_value' => $form_state['my data']->example ? $form_state['my data']->example : default, + '#options' => array( + 'default' => t('Default'), + 'setting1' => t('Setting1'), + ), + ); + + return $form; +} + +/** + * Submit handler to prepare needed values for storing in cache. + */ +function wizardid_step2_form_submit($form, &$form_state) { + $form_state['my data']->example = $form_state['values']['example']; +} ++ +The data is stored in the my data object on submitting. If the user goes back to this step the cached my data is used as the default form value. The function my_module_get_cache() is like the cache functions explained above.
+ +Required fields, cancel and back buttons
+If you have required fields in your forms, the back and cancel buttons will not work as expected since validation of the form will fail. You can add the following code to the top of your form validation to avoid this problem:
++/** + * Validation handler for step2 form + */ +function wizardid_step2_form_validate(&$form, &$form_state) { + // if the clicked button is anything but the normal flow + if ($form_state['clicked_button']['#next'] != $form_state['next']) { + drupal_get_messages('error'); + form_set_error(NULL, '', TRUE); + return; + } + // you form validation goes here + // ... +} ++ +Wizard for anonymous users
+If you are creating a wizard which is be used by anonymous users, you might run into some issues with drupal's caching for anonymous users. You can circumvent this by using hook_init and telling drupal to not cache your wizard pages:
++/** + * Implementation of hook init + */ +function mymodule_init() { + // if the path leads to the wizard + if (drupal_match_path($_GET['q'], 'path/to/your/wizard/*')) { + // set cache to false + $GLOBALS['conf']['cache'] = FALSE; + } +} +diff --git a/sites/all/modules/contrib/ctools/images/arrow-active.png b/sites/all/modules/contrib/ctools/images/arrow-active.png new file mode 100644 index 0000000000000000000000000000000000000000..3bbd3c27f29f9a1781276a2ae8faa7afd5d2d36e GIT binary patch literal 313 zcmeAS@N?(olHy`uVBq!ia0vp^96-#^!3-qdU8!RPQY`6?zK#qG*KS<#k1zuAB}-f* zN`mv#O3D+9QW+dm@{>{(JaZG%Q-e|yQz{EjrrH1%xd!-zxO#bc1qB6pdwbu!dGq1J zhmRgT^6~LGa^%RaUAy+}+xPF^zmq3VUc7kG&(CkurcE0*Y)DB-@%Q&XfByW98#g9T zp6u!A`Tzg_bT_}9Km)5hT^vIyZY2pcGc_>$j@3?%=}IXVGIu?6^qxc>kDAIJ lF_r}R z1v5B2yO9Ruh >$j@3?%=}IXVGIu?6^qxc>kDAIJ lF_r}R z1v5B2yO9Ru2zk0VhE&W+{&76uaKb@_0}N~oA{!VF-#vS9IZ&3t)78&qol`;+0EMF; ATL1t6 literal 0 HcmV?d00001 diff --git a/sites/all/modules/contrib/ctools/images/expanded-options.png b/sites/all/modules/contrib/ctools/images/expanded-options.png new file mode 100644 index 0000000000000000000000000000000000000000..b7b755c0a44f7ebae4972e9489f7d004c0ab82c7 GIT binary patch literal 228 zcmV ww{j7*Vo28PTTa|E`mIR+~;bCfMvxdsyM z(5ekkRsEa&-}g5%GyJU<0Jd$L=lM6Mlp>-Q0E7@k6hipggNVi$A_4$>6U(xA?~miC z(WYsvwNlDgvc9*j>oiUKzCXvV>zs3a-+$74R*X?=Jq&}^I;C`RuQW5~oX2r2rQEI4 eCv_nr-uDldTYa;G_K`FI0000 iq z0IV%C^!fao008>^|L)Drv@$cSevPOvH1+WD^zif8ot?_h()RN6?e+VlH8rWRx4}_S z>Eq+2H$UR#=J@XJx!2^a&*a?J*5Baax6|XJA0GGX>#7wN?d9dM-~7hn fFvN$`qJUpqw(%<6b?d|i_j*rpq z|FJ(m+tbtf@bI-UF}+7e`| ;AXyh1|0zrm`px0$`u?e_b> zOiZ*eF_^v6z=e*`nwsd~;OpVxt2H&U;r{6M{D+mE+1A*$J3IUS{=tTi+0xSE HoMkHoiwksV_9Fv$(S{F{rV(y2#O+9Z9zU00A*cL_t(|+F}r6KmlR^ z0mPunD6P)MEzZd)&dsJS&8QC$Knx6gJZzi-qWt`#0tRe6=92&dh=GBJO-fUdiJ6&6 zQB$ftgasggfGQFuM{AgHFjZ;vSL?*F00a;N19zC39-~Z5eiWmhntvAyKmai? 1%$tYfNmRsaYfhDz6Tm24#=X67Cx zJC%4#V}Jl+FzibdoXEs1B*e_5E!b-{1t5SJio=_PMYPN$CB3vngq>{50Ro6YwaBAJ zUP6wSS588{BDl;2Ab=RAdUCO{va$mKkmBNM0SGVv{T(ma@e6-+00000NkvXXu0mjf Dl_$#h literal 0 HcmV?d00001 diff --git a/sites/all/modules/contrib/ctools/images/icon-configure.png b/sites/all/modules/contrib/ctools/images/icon-configure.png new file mode 100644 index 0000000000000000000000000000000000000000..e23d67cc04b84880d0437e23ffcba837d2dc4121 GIT binary patch literal 765 zcmV -74EqOkY-{rmp^hMTIQ#@mCazW_yW+u-8> zPk}pYiU37!Rg=5p tkC8 MYI{+_ ;Luo{p 4KD>lDEyR&Etl&(Yn*)-~a#n{r!cl#v)~o)A;<=?*HNB<)Ead ztIy)#^#A()|Df6X+~MQJ*yDq%#H88$+~DJ&z|&y3EA;>X0V+vEK~#9!Vlc30L<05( z00G3n&A`LT!lJ;ypuoby%ERCa5I_u^+3YL~3=E2jK!SxmT@xUH7#LYu81xtzq@@{v zRB$0PKmai?vM}h|TJVXA@>$sGa|kj61Q1Ao!Aix>(9llBiUBA95I_u!> QW{q zQtA>69J$N@0mQ(_5G$l?V<|3fX`?LUA;t_4Knx)a$=0%3W(*8wTCpV$1*m#K4#p z$iS~1;Ow5F&CigO#|#ia42)@EI!Yn}8X5v3N;;7V%m4wz!045zrYp>4WW*(`tL7KS z3=lvJjGn%#a>fj%rVPe%s{T=$00G3n8RT7%DsRrqYc3z^mXpa05I_vv(M9oWk}{5t vGLmd;K28|`0mKjy&IAGu4q(b91|Yxy=@24rSbWX-00000NkvXXu0mjfu!oSb literal 0 HcmV?d00001 diff --git a/sites/all/modules/contrib/ctools/images/icon-delete.png b/sites/all/modules/contrib/ctools/images/icon-delete.png new file mode 100644 index 0000000000000000000000000000000000000000..5f0cf695b0cac487efecfd7eae66e7492a2ba306 GIT binary patch literal 877 zcmV-z1CsoSP) iq z0IV%C^!fao008>^|L)Drv@$cSevPOvH1+WD^zif8ot?_h()RN6?e+VlH8rWRx4}_S z>Eq+2H$UR#=J@XJx!2^a&*a?J*5Baax6|XJA0GGX>#7wN?d9dM-~7hn fFvN$`qJUpqw(%<6b?d|i_j*rpq z|FJ(m+tbtf@bI-UF}+7e`| ;AXyh1|0zrm`px0$`u?e_b> zOiZ*eF_^v6z=e*`nwsd~;OpVxt2H&U;r{6M{D+mE+1A*$J3IUS{=tTi+0xSE HoMkHoiwksV_9Fv$(S{F{rV(y2#O+9Z9zU00A*cL_t(|+F}r6KmlR^ z0mPunD6P)MEzZd)&dsJS&8QC$Knx6gJZzi-qWt`#0tRe6=92&dh=GBJO-fUdiJ6&6 zQB$ftgasggfGQFuM{AgHFjZ;vSL?*F00a;N19zC39-~Z5eiWmhntvAyKmai? 1%$tYfNmRsaYfhDz6Tm24#=X67Cx zJC%4#V}Jl+FzibdoXEs1B*e_5E!b-{1t5SJio=_PMYPN$CB3vngq>{50Ro6YwaBAJ zUP6wSS588{BDl;2Ab=RAdUCO{va$mKkmBNM0SGVv{T(ma@e6-+00000NkvXXu0mjf Dl_$#h literal 0 HcmV?d00001 diff --git a/sites/all/modules/contrib/ctools/images/no-icon.png b/sites/all/modules/contrib/ctools/images/no-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..fa78ec179a83428e5b8e247890278cdf91a8cb3e GIT binary patch literal 574 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl;=8&~`zjDUQ}64!{5 z;QX|b^2DN42FH~Aq*MjZ+{E S{rGmAj8jfaD?v!i2UYjjvlpQJ*A1Jfmibqf^i?VA_L7&|Z+X1sot(Xnwu z!w#NRT}4?8j7Jun OdXEg zItmI4IG$*KURlh B>e|rlEqG%3qu@s@KxgP$`nu(@>FI?n*t0}KS|4Q7%GJhQ zr!}-TZTjTdv1#K+psr@Qb}IpmMn>t`v&?`hUc4+UFfd-t$f&NazkZvXLjxnjnNaC9 Um!If;0EPpDr>mdKI;Vst09}j(6951J literal 0 HcmV?d00001 diff --git a/sites/all/modules/contrib/ctools/images/status-active.gif b/sites/all/modules/contrib/ctools/images/status-active.gif new file mode 100644 index 0000000000000000000000000000000000000000..207e95c3fa8cc31a89af150ad74059b1667922b6 GIT binary patch literal 2196 zcmeH{Urbw79LLW&_xASmFQt@HSs}NhO2-Jh1BM+kb4v^B62vmdY!-sIxUi{Y-eKaZ zd%2gEe>0H6h_VE=!^E(JL`)>X2jVS^Zcfvn5<-k2WvFCNG`ghG7rZ6$WnPVsKJ5GO zJ8$RvJD>CY{Z5~0cApFxfB|GdPWa6w{k0|AwWK|oJQ2I!Je@i}^Zo0eZod2F_QJwK zJhi*9v9}~^YcH8hrc$Ymjg76Xt@{st+t|%+?PYgm$u+ybCo8+3&1pZopVPthPs$Sl z|6c-*eO+r)N4wV(XsWJa2q<^z_?=Wy@>!YdIyWW&Uj-18tONF|3Mu0pM+@E|b#0}^ z!~zi%9q%)3&a(p~G`B1c3*4%%lzk?{ByJ6=pegKss;|dO;n%+FeCW7{V%4V@w $Z{GnR7Ggdfd96mCSRsm(IU3X`eH-@mY!nv5{mPH7adf{@tFd+{D#Z*P7E4Cu( zNI<&yxF8`-(K~6DmV_bgDJSM6QPSy-`a53zHsK*ro@??84uYGYiss#G335cQ(q ^ z%wp~p0g$crQ*_&{2!a61R3ETvWG(*SZ8O_GR^u5Yc|OD{*}kN}6 zL0fe!xt};GOz%hKaYMskC&qRw=<`U&U>^cAo zLBAqcYczaZ@J3LHlNVx%1c};=4Y#YG7RXW%xDepEe!!I1`hwd%{Td?N=QPn}&`2C( ze3|@EBBE1Mq|) `TYFdd2uA0l e6o z6d!7|&nD=vC$NZI_5Tu^++t8 Y=|vzw z1Q9I-fe?vcx1AJNZgm0}! B}H#d-&TI?-^oT* zRA10W&q#7unMxBUvZJU0H>48~K*CV@D7`H;MzIa8(QDnFN!<(7r69X=cc1~zTKw(l Pm4SkyKc+`yv*-Q _u^kz-dM?gkqO)PO{US_lf1Q-uHl0&)lliU%<&c*`LW1i}##kTZlc zpd^qOq7@Mj&}tDx5DSADxbdc6R6U`F+3NndSNW zI=jTO02c5X06u*9u $cklhk Dxp2%t?rYW@LT lxy+ z)RqGpOy62E^M;v^0?;GFe*gGb&D7u{0bgR-;ENeGg^@fJ;U|}nHA`1d*l--}?5^%o z?==@yT>6e TOB>Hs C*F4 zCxvu!N-EDI8lr8ZL%At_f_$_6h^L2~Lye57kCC}iNif)~sXy$d(JTFQh3ccn8rscj zIA=~LvCI7u3l1hkyZdF?W)r;yXRD|e<$`aCwI+hTV4rQSP)!yudcAFz$6e2V1c%lq z=060cVgx;Lwk}US61Tm{I<&dy^_B@U{I(5VI+DK?_{w`)!$%zkv)QL+*rS`L29HGh zI?@|_%WkT}kRC<2fWbQ#AUWUl^jM(2K{#&ma*-r@S1R*yiJ}KHf><#aoxZZh7#B}n zGOA-ypc9Zi _bo8I}nK&vq>UoKOM?ixSb=VmC$z<@wTONGQk4( zY ;e`J#HfFYf9^)#aKU zqHa9ir1~l)t)%h#K->Y*tvEUFe%?f6uevpR5_s16Bqok{w63+}1<= pW)MZ%+CHpEMi)`82+@{WSg~d!D0ky<~BvDy;9MB7A9)VtZg-de04a7O`&F zaV&^_ag4?pHnx@+(^!EW^PBxi`7d`#27PpWo9L$?crUJrprP5&hOKlqC1*n$-2bB) z2}#D>2$D!d%HZThF{!kI)f{k3idh=8!<}5urXCJ%YAUFpmTN`L`gHl%$MgqU^v!3Q z)a*{vy7Tl4zOC^^_T37OtUxT)Ui&8cl#;0HalV{d_^rH%Dmr|1WdU9=>VE$yFagx{ zU3&1tW1#gS^QrTzN73%BMN{}y4aNDj^hk^}#K`jWd5yXJ(wupp5&+ItFq8fkhI(pn zo3T%XVM7(`R%4hnRuS%Hc&B+TF@ACHa;r0y&08pMEM>J8GLRl4UFc)aHP|h_Ttd&i znmKw42rynU|L;pYV=F0olZJ?t4GsqW?2!8pD07q1(D}=jk a+F-^;0 zgEF>NSbYP5HL6gUS|Ta(Q0X!4k%psKljg{>LuYtZ>`ukra~C*TS}oBY5Vl9+Nc87( z{4Z;FrR|VGJ*k5PC0w>KqknNJAQ{~-F_Fe^C`mkxKdql!9=}n?F1hu5_dAa}_{80T zeYYL941O8Z-TY_zq1o&okKaZ&HE3n+qY F&d^<1SkeJO8b0+4j2p>)& zt+CsSka>%Olf2>Bji!lEn%PcotDu@%0 ~b1f?QxK z<}c)0@nr=BSX2zBWUP=CE5LF&zY;8s7daI+x`L@Wd 6y%_y~p%ph#hm*BF6Z17-fFCUNMl zEXd;(PQKR6OeEe;bX;)ujy1@IRRQ!(D+77ZA4^D{&Hv)B;OB`L)4;M)=L}JSyjYQ0 zsANaw@kt=)VwUTw)@5@tn%PuX2f{Pt5aLB?Yu?G&jDaQ~f{+8%g3xRP!}`$c0JRq> zlfgrn@pTl3d;1E|p>E(lLGEqmgF_GS$9PAt>__bBqwgnsQ%#4E#r?{VvAVfYM%WTc z|Kv!{0XvINQ%ITZ;N}0xfy9+U&hxaJ?mo!PU@#$6tIbc2A&^!KX=jqaxD0_ti^AUB zCJ+ertY6>(Yjp$}MRmw@2KvG5Nb4J@@d$#`?iM09h9?NP--Qd__yK?7{gD3{Ed$=* z&%WVnCF RG(c#+1V;Tm3mWko4y!QfEu}ONuZZ1Q^B2$_qq}wDM}KLQz~=Q+Svw zaixMYB2X0mC<4*zXGD mpVdQH~RDGNLxy`}>}G~g((CUueZO~iQg zdw&-h`Q|ZTeB~Z4e3MK}g|Fa0xH5U05Z+_Hp#Qx7?Io|TR9!$$qZ5TOeAN3>5oojd zAOBAwpkP ixysnC>g}uu97fZ*{yR)}3HS@8oB8E(T Txa$TC-%9gHCRT!XTiG;(VzH(8tsob4bNnE1rC?>OE{b^@s2 z1z^U)#@4o65OfN0w~L_M0i*qV<>M|Z@StusZJc-QyGic&j@Q6`9rMns<+khJIni5Q znoF(cenyIveQE$Y8~(o<5P|(?1*ts@ML^f~x2u*W@|{X)ZAwgd(BDwpZ)A-c(+mS| z`<+;$;$coy(!gi@D25i#ecIS2w{=nrPw1GLBt%pZcc+_9PEIB#*g3JH9Fbgqw~hIx zNhln=(1)5HtW?TeBFTulss4~llg^>x5Cc-R)VC?C-6E^Fb~F$acRDecl=D3`nKl=2 zs~)(}Bxuiq4*cACSiF0vPp}}W8+64p*MAXdGq}s>C&-w;aV+?H0v~wQzG|f{Fr~aM j{1wO?+BdBIaRHHSihHZcULyxlisN(x+-Cj*K3@L@ffBmW literal 0 HcmV?d00001 diff --git a/sites/all/modules/contrib/ctools/includes/action-links.theme.inc b/sites/all/modules/contrib/ctools/includes/action-links.theme.inc new file mode 100644 index 0000000..3a2398a --- /dev/null +++ b/sites/all/modules/contrib/ctools/includes/action-links.theme.inc @@ -0,0 +1,33 @@ + 'links', + 'file' => 'includes/action-links.theme.inc', + ); +} + +/** + * Render a menu local actions wrapper. + * + * @param $links + * Local actions links. + * @param $attributes + * An array of attributes to append to the wrapper. + */ +function theme_ctools_menu_local_actions_wrapper($variables) { + $links = drupal_render($variables['links']); + + if (empty($links)) { + return; + } + + return ' ' . $links . '
'; +} \ No newline at end of file diff --git a/sites/all/modules/contrib/ctools/includes/ajax.inc b/sites/all/modules/contrib/ctools/includes/ajax.inc new file mode 100644 index 0000000..96f5068 --- /dev/null +++ b/sites/all/modules/contrib/ctools/includes/ajax.inc @@ -0,0 +1,157 @@ + $image)), $dest, $alt, $class); +} + +/** + * Render text as a link. This will automatically apply an AJAX class + * to the link and add the appropriate javascript to make this happen. + * + * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will + * not use user input so this is a very minor concern. + * + * @param $text + * The text that will be displayed as the link. + * @param $dest + * The destination of the link. + * @param $alt + * The alt text of the link. + * @param $class + * Any class to apply to the link. @todo this should be a options array. + * @param $type + * A type to use, in case a different behavior should be attached. Defaults + * to ctools-use-ajax. + */ +function ctools_ajax_text_button($text, $dest, $alt, $class = '', $type = 'use-ajax') { + drupal_add_library('system', 'drupal.ajax'); + return l($text, $dest, array('html' => TRUE, 'attributes' => array('class' => array($type, $class), 'title' => $alt))); +} + +/** + * Render an icon and related text as a link. This will automatically apply an AJAX class + * to the link and add the appropriate javascript to make this happen. + * + * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will + * not use user input so this is a very minor concern. + * + * @param $text + * The text that will be displayed as the link. + * @param $image + * The icon image to include in the link. + * @param $dest + * The destination of the link. + * @param $alt + * The title text of the link. + * @param $class + * Any class to apply to the link. @todo this should be a options array. + * @param $type + * A type to use, in case a different behavior should be attached. Defaults + * to ctools-use-ajax. + */ +function ctools_ajax_icon_text_button($text, $image, $dest, $alt, $class = '', $type = 'use-ajax') { + drupal_add_library('system', 'drupal.ajax'); + $rendered_image = theme('image', array('path' => $image)); + $link_content = $rendered_image . "" . $text . ""; + return l($link_content, $dest, array('html' => TRUE, 'attributes' => array('class' => array($type, $class), 'title' => $alt))); +} + +/** + * Set a single property to a value, on all matched elements. + * + * @param $selector + * The CSS selector. This can be any selector jquery uses in $(). + * @param $name + * The name or key: of the data attached to this selector. + * @param $value + * The value of the data. + */ +function ctools_ajax_command_attr($selector, $name, $value) { + ctools_add_js('ajax-responder'); + return array( + 'command' => 'attr', + 'selector' => $selector, + 'name' => $name, + 'value' => $value, + ); + } + +/** + * Force a client-side redirect. + * + * @param $url + * The url to be redirected to. This can be an absolute URL or a + * Drupal path. + * @param $delay + * A delay before applying the redirection, in milliseconds. + * @param $options + * An array of options to pass to the url() function. + */ +function ctools_ajax_command_redirect($url, $delay = 0, $options = array()) { + ctools_add_js('ajax-responder'); + return array( + 'command' => 'redirect', + 'url' => url($url, $options), + 'delay' => $delay, + ); +} + +/** + * Force a reload of the current page. + */ +function ctools_ajax_command_reload() { + ctools_add_js('ajax-responder'); + return array( + 'command' => 'reload', + ); +} + +/** + * Submit a form. + * + * This is useful for submitting a parent form after a child form has finished + * processing in a modal overlay. + * + * @param $selector + * The CSS selector to identify the form for submission. This can be any + * selector jquery uses in $(). + */ +function ctools_ajax_command_submit($selector) { + ctools_add_js('ajax-responder'); + return array( + 'command' => 'submit', + 'selector' => $selector, + ); +} + +/** + * Send an error response back via AJAX and immediately exit. + */ +function ctools_ajax_render_error($error = '') { + $commands = array(); + $commands[] = ajax_command_alert($error); + print ajax_render($commands); + exit; +} + diff --git a/sites/all/modules/contrib/ctools/includes/cache.inc b/sites/all/modules/contrib/ctools/includes/cache.inc new file mode 100644 index 0000000..3918683 --- /dev/null +++ b/sites/all/modules/contrib/ctools/includes/cache.inc @@ -0,0 +1,169 @@ + TRUE, + 'ignore words' => array(), + 'separator' => '-', + 'replacements' => array(), + 'transliterate' => FALSE, + 'reduce ascii' => TRUE, + 'max length' => FALSE, + 'lower case' => FALSE, + ); + + // Allow modules to make other changes to the settings. + if (isset($settings['clean id'])) { + drupal_alter('ctools_cleanstring_' . $settings['clean id'], $settings); + } + + drupal_alter('ctools_cleanstring', $settings); + + $output = $string; + + // Do any replacements the user selected up front. + if (!empty($settings['replacements'])) { + $output = strtr($output, $settings['replacements']); + } + + // Remove slashes if instructed to do so. + if ($settings['clean slash']) { + $output = str_replace('/', '', $output); + } + + if (!empty($settings['transliterate']) && module_exists('transliteration')) { + $output = transliteration_get($output); + } + + // Reduce to the subset of ASCII96 letters and numbers + if ($settings['reduce ascii']) { + $pattern = '/[^a-zA-Z0-9\/]+/'; + $output = preg_replace($pattern, $settings['separator'], $output); + } + + // Get rid of words that are on the ignore list + if (!empty($settings['ignore words'])) { + $ignore_re = '\b' . preg_replace('/,/', '\b|\b', $settings['ignore words']) . '\b'; + + if (function_exists('mb_eregi_replace')) { + $output = mb_eregi_replace($ignore_re, '', $output); + } + else { + $output = preg_replace("/$ignore_re/i", '', $output); + } + } + + // Always replace whitespace with the separator. + $output = preg_replace('/\s+/', $settings['separator'], $output); + + // In preparation for pattern matching, + // escape the separator if and only if it is not alphanumeric. + if (isset($settings['separator'])) { + if (preg_match('/^[^' . CTOOLS_PREG_CLASS_ALNUM . ']+$/uD', $settings['separator'])) { + $seppattern = $settings['separator']; + } + else { + $seppattern = '\\' . $settings['separator']; + } + // Trim any leading or trailing separators (note the need to + $output = preg_replace("/^$seppattern+|$seppattern+$/", '', $output); + + // Replace multiple separators with a single one + $output = preg_replace("/$seppattern+/", $settings['separator'], $output); + } + + // Enforce the maximum component length + if (!empty($settings['max length'])) { + $output = ctools_cleanstring_truncate($output, $settings['max length'], $settings['separator']); + } + + if (!empty($settings['lower case'])) { + $output = drupal_strtolower($output); + } + return $output; +} + +/** + * A friendly version of truncate_utf8. + * + * @param $string + * The string to be truncated. + * @param $length + * An integer for the maximum desired length. + * @param $separator + * A string which contains the word boundary such as - or _. + * + * @return + * The string truncated below the maxlength. + */ +function ctools_cleanstring_truncate($string, $length, $separator) { + if (drupal_strlen($string) > $length) { + $string = drupal_substr($string, 0, $length + 1); // leave one more character + if ($last_break = strrpos($string, $separator)) { // space exists AND is not on position 0 + $string = substr($string, 0, $last_break); + } + else { + $string = drupal_substr($string, 0, $length); + } + } + return $string; +} diff --git a/sites/all/modules/contrib/ctools/includes/collapsible.theme.inc b/sites/all/modules/contrib/ctools/includes/collapsible.theme.inc new file mode 100644 index 0000000..f7bbbb3 --- /dev/null +++ b/sites/all/modules/contrib/ctools/includes/collapsible.theme.inc @@ -0,0 +1,79 @@ + array('handle' => NULL, 'content' => NULL, 'collapsed' => FALSE), + 'file' => 'includes/collapsible.theme.inc', + ); + $items['ctools_collapsible_remembered'] = array( + 'variables' => array('id' => NULL, 'handle' => NULL, 'content' => NULL, 'collapsed' => FALSE), + 'file' => 'includes/collapsible.theme.inc', + ); +} + +/** + * Render a collapsible div. + * + * @param $handle + * Text to put in the handle/title area of the div. + * @param $content + * Text to put in the content area of the div, this is what will get + * collapsed + * @param $collapsed = FALSE + * If true, this div will start out collapsed. + */ +function theme_ctools_collapsible($vars) { + ctools_add_js('collapsible-div'); + ctools_add_css('collapsible-div'); + + $class = $vars['collapsed'] ? ' ctools-collapsed' : ''; + $output = ''; + $output .= ''; + + return $output; +} + +/** + * Render a collapsible div whose state will be remembered. + * + * @param $id + * The CSS id of the container. This is required. + * @param $handle + * Text to put in the handle/title area of the div. + * @param $content + * Text to put in the content area of the div, this is what will get + * collapsed + * @param $collapsed = FALSE + * If true, this div will start out collapsed. + */ +function theme_ctools_collapsible_remembered($vars) { + $id = $vars['id']; + $handle = $vars['handle']; + $content = $vars['content']; + $collapsed = $vars['collapsed']; + ctools_add_js('collapsible-div'); + ctools_add_css('collapsible-div'); + + $class = $collapsed ? ' ctools-collapsed' : ''; + $output = '' . $vars['handle'] . ''; + $output .= '' . $vars['content'] . ''; + $output .= ''; + $output .= ''; + + return $output; +} + diff --git a/sites/all/modules/contrib/ctools/includes/content.inc b/sites/all/modules/contrib/ctools/includes/content.inc new file mode 100644 index 0000000..ae1c607 --- /dev/null +++ b/sites/all/modules/contrib/ctools/includes/content.inc @@ -0,0 +1,853 @@ + $plugin['title'], + 'description' => $plugin['description'], + 'icon' => ctools_content_admin_icon($plugin), + 'category' => $plugin['category'], + ); + + if (isset($plugin['required context'])) { + $type['required context'] = $plugin['required context']; + } + if (isset($plugin['top level'])) { + $type['top level'] = $plugin['top level']; + } + $plugin['content types'] = array($plugin['name'] => $type); + if (!isset($plugin['single'])) { + $plugin['single'] = TRUE; + } + } + } +} + +/** + * Fetch metadata on a specific content_type plugin. + * + * @param $content type + * Name of a panel content type. + * + * @return + * An array with information about the requested panel content type. + */ +function ctools_get_content_type($content_type) { + ctools_include('context'); + ctools_include('plugins'); + return ctools_get_plugins('ctools', 'content_types', $content_type); +} + +/** + * Fetch metadata for all content_type plugins. + * + * @return + * An array of arrays with information about all available panel content types. + */ +function ctools_get_content_types() { + ctools_include('context'); + ctools_include('plugins'); + return ctools_get_plugins('ctools', 'content_types'); +} + +/** + * Get all of the individual subtypes provided by a given content type. This + * would be all of the blocks for the block type, or all of the views for + * the view type. + * + * @param $type + * The content type to load. + * + * @return + * An array of all subtypes available. + */ +function ctools_content_get_subtypes($type) { + static $cache = array(); + + $subtypes = array(); + + if (is_array($type)) { + $plugin = $type; + } + else { + $plugin = ctools_get_content_type($type); + } + + if (empty($plugin) || empty($plugin['name'])) { + return; + } + + if (isset($cache[$plugin['name']])) { + return $cache[$plugin['name']]; + } + + if (isset($plugin['content types'])) { + $function = $plugin['content types']; + if (is_array($function)) { + $subtypes = $function; + } + else if (function_exists($function)) { + // Cast to array to prevent errors from non-array returns. + $subtypes = (array) $function($plugin); + } + } + + // Walk through the subtypes and ensure minimal settings are + // retained. + foreach ($subtypes as $id => $subtype) { + // Use exact name since this is a modify by reference. + ctools_content_prepare_subtype($subtypes[$id], $plugin); + } + + $cache[$plugin['name']] = $subtypes; + + return $subtypes; +} + +/** + * Given a content type and a subtype id, return the information about that + * content subtype. + * + * @param $type + * The content type being fetched. + * @param $subtype_id + * The id of the subtype being fetched. + * + * @return + * An array of information describing the content subtype. + */ +function ctools_content_get_subtype($type, $subtype_id) { + $subtype = array(); + if (is_array($type)) { + $plugin = $type; + } + else { + $plugin = ctools_get_content_type($type); + } + + $function = ctools_plugin_get_function($plugin, 'content type'); + if ($function) { + $subtype = $function($subtype_id, $plugin); + } + else { + $subtypes = ctools_content_get_subtypes($type); + if (isset($subtypes[$subtype_id])) { + $subtype = $subtypes[$subtype_id]; + } + // If there's only 1 and we somehow have the wrong subtype ID, do not + // care. Return the proper subtype anyway. + if (empty($subtype) && !empty($plugin['single'])) { + $subtype = current($subtypes); + } + } + + if ($subtype) { + ctools_content_prepare_subtype($subtype, $plugin); + } + return $subtype; +} + +/** + * Ensure minimal required settings on a content subtype exist. + */ +function ctools_content_prepare_subtype(&$subtype, $plugin) { + foreach (array('path', 'js', 'css') as $key) { + if (!isset($subtype[$key]) && isset($plugin[$key])) { + $subtype[$key] = $plugin[$key]; + } + } + + drupal_alter('ctools_content_subtype', $subtype, $plugin); +} + +/** + * Get the content from a given content type. + * + * @param $type + * The content type. May be the name or an already loaded content type plugin. + * @param $subtype + * The name of the subtype being rendered. + * @param $conf + * The configuration for the content type. + * @param $keywords + * An array of replacement keywords that come from outside contexts. + * @param $args + * The arguments provided to the owner of the content type. Some content may + * wish to configure itself based on the arguments the panel or dashboard + * received. + * @param $context + * An array of context objects available for use. + * @param $incoming_content + * Any incoming content, if this display is a wrapper. + * + * @return + * The content as rendered by the plugin. This content should be an array + * with the following possible keys: + * - title: The safe to render title of the content. + * - title_heading: The title heading. + * - content: The safe to render HTML content. + * - links: An array of links associated with the content suitable for + * theme('links'). + * - more: An optional 'more' link (destination only) + * - admin_links: Administrative links associated with the content, suitable + * for theme('links'). + * - feeds: An array of feed icons or links associated with the content. + * Each member of the array is rendered HTML. + * - type: The content type. + * - subtype: The content subtype. These two may be used together as + * module-delta for block style rendering. + */ +function ctools_content_render($type, $subtype, $conf, $keywords = array(), $args = array(), $context = array(), $incoming_content = '') { + if (is_array($type)) { + $plugin = $type; + } + else { + $plugin = ctools_get_content_type($type); + } + + $subtype_info = ctools_content_get_subtype($plugin, $subtype); + + $function = ctools_plugin_get_function($subtype_info, 'render callback'); + if (!$function) { + $function = ctools_plugin_get_function($plugin, 'render callback'); + } + + if ($function) { + $pane_context = ctools_content_select_context($plugin, $subtype, $conf, $context); + if ($pane_context === FALSE) { + return; + } + + $content = $function($subtype, $conf, $args, $pane_context, $incoming_content); + + if (empty($content)) { + return; + } + + // Set up some defaults and other massaging on the content before we hand + // it back to the caller. + if (!isset($content->type)) { + $content->type = $plugin['name']; + } + + if (!isset($content->subtype)) { + $content->subtype = $subtype; + } + + // Override the title if configured to + if (!empty($conf['override_title'])) { + // Give previous title as an available substitution here. + $keywords['%title'] = empty($content->title) ? '' : $content->title; + $content->original_title = $keywords['%title']; + $content->title = $conf['override_title_text']; + $content->title_heading = isset($conf['override_title_heading']) ? $conf['override_title_heading'] : 'h2'; + } + + if (!empty($content->title)) { + // Perform substitutions + if (!empty($keywords) || !empty($context)) { + $content->title = ctools_context_keyword_substitute($content->title, $keywords, $context); + } + + // Sterilize the title + $content->title = filter_xss_admin($content->title); + + // If a link is specified, populate. + if (!empty($content->title_link)) { + if (!is_array($content->title_link)) { + $url = array('href' => $content->title_link); + } + else { + $url = $content->title_link; + } + // set defaults so we don't bring up notices + $url += array('href' => '', 'attributes' => array(), 'query' => array(), 'fragment' => '', 'absolute' => NULL, 'html' => TRUE); + $content->title = l($content->title, $url['href'], $url); + } + } + + return $content; + } +} + +/** + * Determine if a content type can be edited or not. + * + * Some content types simply have their content and no options. This function + * lets a UI determine if it should display an edit link or not. + */ +function ctools_content_editable($type, $subtype, $conf) { + if (empty($type['edit form']) && empty($subtype['edit form'])) { + return FALSE; + } + + $function = FALSE; + + if (!empty($subtype['check editable'])) { + $function = ctools_plugin_get_function($subtype, 'check editable'); + } + elseif (!empty($type['check editable'])) { + $function = ctools_plugin_get_function($type, 'check editable'); + } + + if ($function) { + return $function($type, $subtype, $conf); + } + + return TRUE; +} + +/** + * Get the administrative title from a given content type. + * + * @param $type + * The content type. May be the name or an already loaded content type object. + * @param $subtype + * The subtype being rendered. + * @param $conf + * The configuration for the content type. + * @param $context + * An array of context objects available for use. These may be placeholders. + */ +function ctools_content_admin_title($type, $subtype, $conf, $context = NULL) { + if (is_array($type)) { + $plugin = $type; + } + else if (is_string($type)) { + $plugin = ctools_get_content_type($type); + } + else { + return; + } + + if ($function = ctools_plugin_get_function($plugin, 'admin title')) { + $pane_context = ctools_content_select_context($plugin, $subtype, $conf, $context); + if ($pane_context === FALSE) { + if ($plugin['name'] == $subtype) { + return t('@type will not display due to missing context', array('@type' => $plugin['name'])); + } + return t('@type:@subtype will not display due to missing context', array('@type' => $plugin['name'], '@subtype' => $subtype)); + } + + return $function($subtype, $conf, $pane_context); + } + else if (isset($plugin['admin title'])) { + return $plugin['admin title']; + } + else if (isset($plugin['title'])) { + return $plugin['title']; + } +} + +/** + * Get the proper icon path to use, falling back to default icons if no icon exists. + * + * $subtype + * The loaded subtype info. + */ +function ctools_content_admin_icon($subtype) { + $icon = ''; + + if (isset($subtype['icon'])) { + $icon = $subtype['icon']; + if (!file_exists($icon)) { + $icon = $subtype['path'] . '/' . $icon; + } + } + + if (empty($icon) || !file_exists($icon)) { + $icon = ctools_image_path('no-icon.png'); + } + + return $icon; +} + +/** + * Set up the default $conf for a new instance of a content type. + */ +function ctools_content_get_defaults($plugin, $subtype) { + if (isset($plugin['defaults'])) { + $defaults = $plugin['defaults']; + } + else if (isset($subtype['defaults'])) { + $defaults = $subtype['defaults']; + } + if (isset($defaults)) { + if (is_string($defaults) && function_exists($defaults)) { + if ($return = $defaults($pane)) { + return $return; + } + } + else if (is_array($defaults)) { + return $defaults; + } + } + + return array(); +} + +/** + * Get the administrative title from a given content type. + * + * @param $type + * The content type. May be the name or an already loaded content type object. + * @param $subtype + * The subtype being rendered. + * @param $conf + * The configuration for the content type. + * @param $context + * An array of context objects available for use. These may be placeholders. + */ +function ctools_content_admin_info($type, $subtype, $conf, $context = NULL) { + if (is_array($type)) { + $plugin = $type; + } + else { + $plugin = ctools_get_content_type($type); + } + + if ($function = ctools_plugin_get_function($plugin, 'admin info')) { + $output = $function($subtype, $conf, $context); + } + + if (empty($output) || !is_object($output)) { + $output = new stdClass(); + // replace the _ with " " for a better output + $subtype = check_plain(str_replace("_", " ", $subtype)); + $output->title = $subtype; + $output->content = t('No info available.'); + } + return $output; +} + +/** + * Add the default FAPI elements to the content type configuration form + */ +function ctools_content_configure_form_defaults($form, &$form_state) { + $plugin = $form_state['plugin']; + $subtype = $form_state['subtype']; + $contexts = isset($form_state['contexts']) ? $form_state['contexts'] : NULL; + $conf = $form_state['conf']; + + $add_submit = FALSE; + if (!empty($subtype['required context']) && is_array($contexts)) { + $form['context'] = ctools_context_selector($contexts, $subtype['required context'], isset($conf['context']) ? $conf['context'] : array()); + $add_submit = TRUE; + } + + ctools_include('dependent'); + + // Unless we're not allowed to override the title on this content type, add this + // gadget to all panes. + if (empty($plugin['no title override']) && empty($subtype['no title override'])) { + $form['aligner_start'] = array( + '#markup' => '' . $handle . ''; + $output .= '' . $content . ''; + $output .= '', + ); + $form['override_title'] = array( + '#type' => 'checkbox', + '#default_value' => isset($conf['override_title']) ? $conf['override_title'] : '', + '#title' => t('Override title'), + '#id' => 'override-title-checkbox', + ); + $form['override_title_text'] = array( + '#type' => 'textfield', + '#default_value' => isset($conf['override_title_text']) ? $conf['override_title_text'] : '', + '#size' => 35, + '#id' => 'override-title-textfield', + '#dependency' => array('override-title-checkbox' => array(1)), + '#dependency_type' => 'hidden', + ); + $form['override_title_heading'] = array( + '#type' => 'select', + '#default_value' => isset($conf['override_title_heading']) ? $conf['override_title_heading'] : 'h2', + '#options' => array( + 'h1' => t('h1'), + 'h2' => t('h2'), + 'h3' => t('h3'), + 'h4' => t('h4'), + 'h5' => t('h5'), + 'h6' => t('h6'), + 'div' => t('div'), + 'span' => t('span'), + ), + '#id' => 'override-title-heading', + '#dependency' => array('override-title-checkbox' => array(1)), + '#dependency_type' => 'hidden', + ); + + $form['aligner_stop'] = array( + '#markup' => '', + ); + if (is_array($contexts)) { + $form['override_title_markup'] = array( + '#prefix' => '', + '#suffix' => '', + '#markup' => t('You may use %keywords from contexts, as well as %title to contain the original title.'), + ); + } + $add_submit = TRUE; + } + + if ($add_submit) { + // '#submit' is already set up due to the wizard. + $form['#submit'][] = 'ctools_content_configure_form_defaults_submit'; + } + return $form; +} + +/** + * Submit handler to store context/title override info. + */ +function ctools_content_configure_form_defaults_submit(&$form, &$form_state) { + if (isset($form_state['values']['context'])) { + $form_state['conf']['context'] = $form_state['values']['context']; + } + if (isset($form_state['values']['override_title'])) { + $form_state['conf']['override_title'] = $form_state['values']['override_title']; + $form_state['conf']['override_title_text'] = $form_state['values']['override_title_text']; + $form_state['conf']['override_title_heading'] = $form_state['values']['override_title_heading']; + } +} + +/** + * Get the config form. + * + * The $form_info and $form_state need to be preconfigured with data you'll need + * such as whether or not you're using ajax, or the modal. $form_info will need + * your next/submit callbacks so that you can cache your data appropriately. + * + * @return + * If this function returns false, no form exists. + */ +function ctools_content_form($op, $form_info, &$form_state, $plugin, $subtype_name, $subtype, &$conf, $step = NULL) { + $form_state += array( + 'plugin' => $plugin, + 'subtype' => $subtype, + 'subtype_name' => $subtype_name, + 'conf' => &$conf, + 'op' => $op, + ); + + $form_info += array( + 'id' => 'ctools_content_form', + 'show back' => TRUE, + ); + + // Turn the forms defined in the plugin into the format the wizard needs. + if ($op == 'add') { + if (!empty($subtype['add form'])) { + _ctools_content_create_form_info($form_info, $subtype['add form'], $subtype, $subtype, $op); + } + else if (!empty($plugin['add form'])) { + _ctools_content_create_form_info($form_info, $plugin['add form'], $plugin, $subtype, $op); + } + } + + if (empty($form_info['order'])) { + // Use the edit form for the add form if add form was completely left off. + if (!empty($subtype['edit form'])) { + _ctools_content_create_form_info($form_info, $subtype['edit form'], $subtype, $subtype, $op); + } + else if (!empty($plugin['edit form'])) { + _ctools_content_create_form_info($form_info, $plugin['edit form'], $plugin, $subtype, $op); + } + } + + if (empty($form_info['order'])) { + return FALSE; + } + + ctools_include('wizard'); + return ctools_wizard_multistep_form($form_info, $step, $form_state); + +} + +function _ctools_content_create_form_info(&$form_info, $info, $plugin, $subtype, $op) { + if (is_string($info)) { + if (empty($subtype['title'])) { + $title = t('Configure'); + } + else if ($op == 'add') { + $title = t('Configure new !subtype_title', array('!subtype_title' => $subtype['title'])); + } + else { + $title = t('Configure !subtype_title', array('!subtype_title' => $subtype['title'])); + } + $form_info['order'] = array('form' => $title); + $form_info['forms'] = array( + 'form' => array( + 'title' => $title, + 'form id' => $info, + 'wrapper' => 'ctools_content_configure_form_defaults', + ), + ); + } + else if (is_array($info)) { + $form_info['order'] = array(); + $form_info['forms'] = array(); + $count = 0; + $base = 'step'; + $wrapper = NULL; + foreach ($info as $form_id => $title) { + // @todo -- docs say %title can be used to sub for the admin title. + $step = $base . ++$count; + if (empty($wrapper)) { + $wrapper = $step; + } + + if (is_array($title)) { + if (!empty($title['default'])) { + $wrapper = $step; + } + $title = $title['title']; + } + + $form_info['order'][$step] = $title; + $form_info['forms'][$step] = array( + 'title' => $title, + 'form id' => $form_id, + ); + } + if ($wrapper) { + $form_info['forms'][$wrapper]['wrapper'] = 'ctools_content_configure_form_defaults'; + } + } +} + +/** + * Get an array of all available content types that can be fed into the + * display editor for the add content list. + * + * @param $context + * If a context is provided, content that requires that context can apepar. + * @param $has_content + * Whether or not the display will have incoming content + * @param $allowed_types + * An array of allowed content types (pane types) keyed by content_type . '-' . sub_type + * @param $default_types + * A default allowed/denied status for content that isn't known about + */ +function ctools_content_get_available_types($contexts = NULL, $has_content = FALSE, $allowed_types = NULL, $default_types = NULL) { + $plugins = ctools_get_content_types(); + $available = array(); + + foreach ($plugins as $id => $plugin) { + foreach (ctools_content_get_subtypes($plugin) as $subtype_id => $subtype) { + // exclude items that require content if we're saying we don't + // provide it. + if (!empty($subtype['requires content']) && !$has_content) { + continue; + } + + // Check to see if the content type can be used in this context. + if (!empty($subtype['required context'])) { + if (!ctools_context_match_requirements($contexts, $subtype['required context'])) { + continue; + } + } + + // Check to see if the passed-in allowed types allows this content. + if ($allowed_types) { + $key = $id . '-' . $subtype_id; + if (!isset($allowed_types[$key])) { + $allowed_types[$key] = isset($default_types[$id]) ? $default_types[$id] : $default_types['other']; + } + if (!$allowed_types[$key]) { + continue; + } + } + + // Check if the content type provides an access callback. + if (isset($subtype['create content access']) && function_exists($subtype['create content access']) && !$subtype['create content access']($plugin, $subtype)) { + continue; + } + + // If we made it through all the tests, then we can use this content. + $available[$id][$subtype_id] = $subtype; + } + } + return $available; +} + +/** + * Get an array of all content types that can be fed into the + * display editor for the add content list, regardless of + * availability. + * + */ +function ctools_content_get_all_types() { + $plugins = ctools_get_content_types(); + $available = array(); + + foreach ($plugins as $id => $plugin) { + foreach (ctools_content_get_subtypes($plugin) as $subtype_id => $subtype) { + // If we made it through all the tests, then we can use this content. + $available[$id][$subtype_id] = $subtype; + } + } + return $available; +} + +/** + * Select the context to be used for a piece of content, based upon config. + * + * @param $plugin + * The content plugin + * @param $subtype + * The subtype of the content. + * @param $conf + * The configuration array that should contain the context. + * @param $contexts + * A keyed array of available contexts. + * + * @return + * The matching contexts or NULL if none or necessary, or FALSE if + * requirements can't be met. + */ +function ctools_content_select_context($plugin, $subtype, $conf, $contexts) { + // Identify which of our possible contexts apply. + if (empty($subtype)) { + return; + } + + $subtype_info = ctools_content_get_subtype($plugin, $subtype); + if (empty($subtype_info)) { + return; + } + + if (!empty($subtype_info['all contexts']) || !empty($plugin['all contexts'])) { + return $contexts; + } + + // If the content requires a context, fetch it; if no context is returned, + // do not display the pane. + if (empty($subtype_info['required context'])) { + return; + } + + // Deal with dynamic required contexts not getting updated in the panes. + // For example, Views let you dynamically change context info. While + // we cannot be perfect, one thing we can do is if no context at all + // was asked for, and then was later added but none is selected, make + // a best guess as to what context should be used. THis is right more + // than it's wrong. + if (is_array($subtype_info['required context'])) { + if (empty($conf['context']) || count($subtype_info['required context']) != count($conf['context'])) { + foreach ($subtype_info['required context'] as $index => $required) { + if (!isset($conf['context'][$index])) { + $filtered = ctools_context_filter($contexts, $required); + if ($filtered) { + $keys = array_keys($filtered); + $conf['context'][$index] = array_shift($keys); + } + } + } + } + } + else { + if (empty($conf['context'])) { + $filtered = ctools_context_filter($contexts, $subtype_info['required context']); + if ($filtered) { + $keys = array_keys($filtered); + $conf['context'] = array_shift($keys); + } + } + } + + if (empty($conf['context'])) { + return; + } + + $context = ctools_context_select($contexts, $subtype_info['required context'], $conf['context']); + + return $context; +} + +/** + * Fetch a piece of content from the addressable content system. + * + * @param $address + * A string or an array representing the address of the content. + * @param $type + * The type of content to return. The type is dependent on what + * the content actually is. The default, 'content' means a simple + * string representing the content. However, richer systems may + * offer more options. For example, Panels might allow the + * fetching of 'display' and 'pane' objects. Page Manager + * might allow for the fetching of 'task_handler' objects + * (AKA variants). + */ +function ctools_get_addressable_content($address, $type = 'content') { + if (!is_array($address)) { + $address = explode('::', $address); + } + + if (!$address) { + return; + } + + // This removes the module from the address so the + // implementor is not responsible for that part. + $module = array_shift($address); + return module_invoke($module, 'addressable_content', $address, $type); +} diff --git a/sites/all/modules/contrib/ctools/includes/content.menu.inc b/sites/all/modules/contrib/ctools/includes/content.menu.inc new file mode 100644 index 0000000..f7f9340 --- /dev/null +++ b/sites/all/modules/contrib/ctools/includes/content.menu.inc @@ -0,0 +1,179 @@ + array('access content'), + 'type' => MENU_CALLBACK, + 'file' => 'includes/content.menu.inc', + ); + $items['ctools/autocomplete/%'] = array( + 'page callback' => 'ctools_content_autocomplete_entity', + 'page arguments' => array(2), + ) + $base; +} + +/** + * Helper function for autocompletion of entity titles. + */ +function ctools_content_autocomplete_entity($entity_type, $string = '') { + if ($string != '') { + $entity_info = entity_get_info($entity_type); + + if (!module_exists('entity')) { + module_load_include('inc', 'ctools', 'includes/entity-access'); + _ctools_entity_access($entity_info, $entity_type); + } + + // We must query all ids, because if every one of the 10 don't have access + // the user may never be able to autocomplete a node title. + $preg_matches = array(); + $matches = array(); + $match = preg_match('/\[id: (\d+)\]/', $string, $preg_matches); + if (!$match) { + $match = preg_match('/^id: (\d+)/', $string, $preg_matches); + } + // If an ID match was found, use that ID rather than the whole string. + if ($match) { + $entity_id = $preg_matches[1]; + $results = _ctools_getReferencableEntities($entity_type, $entity_info, $entity_id, '=', 1); + } + else { + // We cannot find results if the entity doesn't have a label to search. + if (!isset($entity_info['entity keys']['label'])) { + drupal_json_output(array("[id: NULL]" => '' . t('Entity Type !entity_type does not support autocomplete search.', array('!entity_type' => $entity_type)) . '')); + return; + } + $results = _ctools_getReferencableEntities($entity_type, $entity_info, $string, 'LIKE', 10); + } + foreach ($results as $entity_id => $result) { + $matches[$result['label'] . " [id: $entity_id]"] = '' . check_plain($result['label']) . ''; + $matches[$result['label'] . " [id: $entity_id]"] .= isset($result['bundle']) ? ' (' . check_plain($result['bundle']) . ')' : ''; + } + + drupal_json_output($matches); + } +} + +/* + * Use well known/tested entity reference code to build our search query + * From EntityReference_SelectionHandler_Generic class + */ +function _ctools_buildQuery($entity_type, $entity_info, $match = NULL, $match_operator = 'CONTAINS') { + $base_table = $entity_info['base table']; + $label_key = $entity_info['entity keys']['label']; + $query = db_select($base_table) + ->fields($base_table, array($entity_info['entity keys']['id'])); + + if (isset($match)) { + if (isset($label_key)) { + $query->condition($base_table . '.' . $label_key, '%' . $match . '%', $match_operator); + } + // This should never happen, but double check just in case. + else { + return array(); + } + } + // Add a generic entity access tag to the query. + $query->addTag('ctools'); + + // We have to perform two checks. First check is a query alter (with tags) + // in an attempt to only return results that have access. However, this is + // not full-proof since entities many not implement hook_access query tag. + // This is why we have a second check after entity load, before we display + // the label of an entity. + if ($entity_type == 'comment') { + // Adding the 'comment_access' tag is sadly insufficient for comments: core + // requires us to also know about the concept of 'published' and + // 'unpublished'. + if (!user_access('administer comments')) { + $query->condition('comment.status', COMMENT_PUBLISHED); + } + + // Join to a node if the user does not have node access bypass permissions + // to obey node published permissions + if (!user_access('bypass node access')) { + $node_alias = $query->innerJoin('node', 'n', '%alias.nid = comment.nid'); + $query->condition($node_alias . '.status', NODE_PUBLISHED); + } + $query->addTag('node_access'); + } + else { + $query->addTag($entity_type . '_access'); + } + + // Add the sort option. + if (isset($label_key)) { + $query->orderBy($base_table . '.' . $label_key, 'ASC'); + } + + return $query; +} + +/** + * Private function to get referencable entities. Based on code from the + * Entity Reference module. + */ +function _ctools_getReferencableEntities($entity_type, $entity_info, $match = NULL, $match_operator = 'LIKE', $limit = 0) { + global $user; + $account = $user; + $options = array(); + // We're an entity ID, return the id + if (is_numeric($match) && $match_operator == '=') { + if ($entity = array_shift(entity_load($entity_type, array($match)))) { + if (isset($entity_info['access callback']) && function_exists($entity_info['access callback'])) { + if ($entity_info['access callback']('view', $entity, $account, $entity_type)) { + $label = entity_label($entity_type, $entity); + return array( + $match => array( + 'label' => !empty($label) ? $label : $entity->{$entity_info['entity keys']['id']}, + 'bundle' => !empty($entity_info['entity keys']['bundle']) ? check_plain($entity->{$entity_info['entity keys']['bundle']}) : NULL, + ), + ); + } + } + } + // If you don't have access, or an access callback or a valid entity, just + // Return back the Entity ID. + return array( + $match => array( + 'label' => $match, + 'bundle' => NULL, + ), + ); + } + + // We have matches, build a query to fetch the result. + if ($query = _ctools_buildQuery($entity_type, $entity_info, $match, $match_operator)) { + if ($limit > 0) { + $query->range(0, $limit); + } + + $results = $query->execute(); + + if (!empty($results)) { + foreach ($results as $record) { + $entities = entity_load($entity_type, array($record->{$entity_info['entity keys']['id']})); + $entity = array_shift($entities); + if (isset($entity_info['access callback']) && function_exists($entity_info['access callback'])) { + if ($entity_info['access callback']('view', $entity, $account, $entity_type)) { + $label = entity_label($entity_type, $entity); + $options[$record->{$entity_info['entity keys']['id']}] = array( + 'label' => !empty($label) ? $label : $entity->{$entity_info['entity keys']['id']}, + 'bundle' => !empty($entity_info['entity keys']['bundle']) ? check_plain($entity->{$entity_info['entity keys']['bundle']}) : NULL, + ); + } + } + } + } + return $options; + } + return array(); +} diff --git a/sites/all/modules/contrib/ctools/includes/content.plugin-type.inc b/sites/all/modules/contrib/ctools/includes/content.plugin-type.inc new file mode 100644 index 0000000..a0debc3 --- /dev/null +++ b/sites/all/modules/contrib/ctools/includes/content.plugin-type.inc @@ -0,0 +1,17 @@ + FALSE, + 'process' => array( + 'function' => 'ctools_content_process', + 'file' => 'content.inc', + 'path' => drupal_get_path('module', 'ctools') . '/includes', + ), + ); +} \ No newline at end of file diff --git a/sites/all/modules/contrib/ctools/includes/content.theme.inc b/sites/all/modules/contrib/ctools/includes/content.theme.inc new file mode 100644 index 0000000..ae4456a --- /dev/null +++ b/sites/all/modules/contrib/ctools/includes/content.theme.inc @@ -0,0 +1,21 @@ + array( + * 0 => array( + * 'name' => 'name of access plugin', + * 'settings' => array(), // These will be set by the form + * ), + * // ... as many as needed + * ), + * 'logic' => 'AND', // or 'OR', + * ), + * @endcode + * + * To add this widget to your UI, you need to do a little bit of setup. + * + * The form will utilize two callbacks, one to get the cached version + * of the access settings, and one to store the cached version of the + * access settings. These will be used from AJAX forms, so they will + * be completely out of the context of this page load and will not have + * knowledge of anything sent to this form (the 'module' and 'argument' + * will be preserved through the URL only). + * + * The 'module' is used to determine the location of the callback. It + * does not strictly need to be a module, so that if your module defines + * multiple systems that use this callback, it can use anything within the + * module's namespace it likes. + * + * When retrieving the cache, the cache may not have already been set up; + * In order to efficiently use cache space, we want to cache the stored + * settings *only* when they have changed. Therefore, the get access cache + * callback should first look for cache, and if it finds nothing, return + * the original settings. + * + * The callbacks: + * - $module . _ctools_access_get($argument) -- get the 'access' settings + * from cache. Must return array($access, $contexts); This callback can + * perform access checking to make sure this URL is not being gamed. + * - $module . _ctools_access_set($argument, $access) -- set the 'access' + * settings in cache. + * - $module . _ctools_access_clear($argument) -- clear the cache. + * + * The ctools_object_cache is recommended for this purpose, but you can use + * any caching mechanism you like. An example: + * + * @code{ + * ctools_include('object-cache'); + * ctools_object_cache_set("$module:argument", $access); + * } + * + * To utilize this form: + * @code + * ctools_include('context-access-admin'); + * $form_state = array( + * 'access' => $access, + * 'module' => 'module name', + * 'callback argument' => 'some string', + * 'contexts' => $contexts, // an array of contexts. Optional if no contexts. + * // 'logged-in-user' will be added if not present as the access system + * // requires this context. + * ), + * $output = drupal_build_form('ctools_access_admin_form', $form_state); + * if (!empty($form_state['executed'])) { + * // save $form_state['access'] however you like. + * } + * @endcode + * + * Additionally, you may add 'no buttons' => TRUE if you wish to embed this + * form into your own, and instead call + * + * @code{ + * $form = ctools_access_admin_form($form, $form_state); + * } + * + * You'll be responsible for adding a submit button. + * + * You may use ctools_access($access, $contexts) which will return + * TRUE if access is passed or FALSE if access is not passed. + */ + +/** + * Administrative form for access control. + */ +function ctools_access_admin_form($form, &$form_state) { + ctools_include('context'); + $argument = isset($form_state['callback argument']) ? $form_state['callback argument'] : ''; + $fragment = $form_state['module']; + if ($argument) { + $fragment .= '-' . $argument; + } + + $contexts = isset($form_state['contexts']) ? $form_state['contexts'] : array(); + + $form['access_table'] = array( + '#markup' => ctools_access_admin_render_table($form_state['access'], $fragment, $contexts), + ); + + $form['add-button'] = array( + '#theme' => 'ctools_access_admin_add', + ); + // This sets up the URL for the add access modal. + $form['add-button']['add-url'] = array( + '#attributes' => array('class' => array("ctools-access-add-url")), + '#type' => 'hidden', + '#value' => url("ctools/context/ajax/access/add/$fragment", array('absolute' => TRUE)), + ); + + $plugins = ctools_get_relevant_access_plugins($contexts); + $options = array(); + foreach ($plugins as $id => $plugin) { + $options[$id] = $plugin['title']; + } + + asort($options); + + $form['add-button']['type'] = array( + // This ensures that the form item is added to the URL. + '#attributes' => array('class' => array("ctools-access-add-url")), + '#type' => 'select', + '#options' => $options, + '#required' => FALSE, + ); + + $form['add-button']['add'] = array( + '#type' => 'submit', + '#attributes' => array('class' => array('ctools-use-modal')), + '#id' => "ctools-access-add", + '#value' => t('Add'), + ); + + $form['logic'] = array( + '#type' => 'radios', + '#options' => array( + 'and' => t('All criteria must pass.'), + 'or' => t('Only one criteria must pass.'), + ), + '#default_value' => isset($form_state['access']['logic']) ? $form_state['access']['logic'] : 'and', + ); + + if (empty($form_state['no buttons'])) { + $form['buttons']['save'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#submit' => array('ctools_access_admin_form_submit'), + ); + } + + return $form; +} + +/** + * Render the table. This is used both to render it initially and to rerender + * it upon ajax response. + */ +function ctools_access_admin_render_table($access, $fragment, $contexts) { + ctools_include('ajax'); + ctools_include('modal'); + $rows = array(); + + if (empty($access['plugins'])) { + $access['plugins'] = array(); + } + + foreach ($access['plugins'] as $id => $test) { + $row = array(); + $plugin = ctools_get_access_plugin($test['name']); + $title = isset($plugin['title']) ? $plugin['title'] : t('Broken/missing access plugin %plugin', array('%plugin' => $test['name'])); + + $row[] = array('data' => $title, 'class' => array('ctools-access-title')); + + $description = ctools_access_summary($plugin, $contexts, $test); + $row[] = array('data' => $description, 'class' => array('ctools-access-description')); + + $operations = ctools_modal_image_button(ctools_image_path('icon-configure.png'), "ctools/context/ajax/access/configure/$fragment/$id", t('Configure settings for this item.')); + $operations .= ctools_ajax_image_button(ctools_image_path('icon-delete.png'), "ctools/context/ajax/access/delete/$fragment/$id", t('Remove this item.')); + + $row[] = array('data' => $operations, 'class' => array('ctools-access-operations'), 'align' => 'right'); + + $rows[] = $row; + } + + $header = array( + array('data' => t('Title'), 'class' => array('ctools-access-title')), + array('data' => t('Description'), 'class' => array('ctools-access-description')), + array('data' => '', 'class' => array('ctools-access-operations'), 'align' => 'right'), + ); + + if (empty($rows)) { + $rows[] = array(array('data' => t('No criteria selected, this test will pass.'), 'colspan' => count($header))); + } + + ctools_modal_add_js(); + return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'ctools-access-table'))); +} + +/** + * Theme the 'add' portion of the access form into a table. + */ +function theme_ctools_access_admin_add($vars) { + $rows = array(array(drupal_render_children($vars['form']))); + $output = ''; + $output .= theme('table', array('rows' => $rows)); + $output .= ''; + return $output; +} + +function ctools_access_admin_form_submit($form, &$form_state) { + $form_state['access']['logic'] = $form_state['values']['logic']; + + $function = $form_state['module'] . '_ctools_access_clear'; + if (function_exists($function)) { + $function($form_state['callback argument']); + } +} + +// -------------------------------------------------------------------------- +// AJAX menu entry points. + +/** + * AJAX callback to add a new access test to the list. + */ +function ctools_access_ajax_add($fragment = NULL, $name = NULL) { + ctools_include('ajax'); + ctools_include('modal'); + ctools_include('context'); + + if (empty($fragment) || empty($name)) { + ctools_ajax_render_error(); + } + + $plugin = ctools_get_access_plugin($name); + if (empty($plugin)) { + ctools_ajax_render_error(); + } + + // Separate the fragment into 'module' and 'argument' + if (strpos($fragment, '-') === FALSE) { + $module = $fragment; + $argument = NULL; + } + else { + list($module, $argument) = explode('-', $fragment, 2); + } + + $function = $module . '_ctools_access_get'; + if (!function_exists($function)) { + ctools_ajax_render_error(t('Missing callback hooks.')); + } + + list($access, $contexts) = $function($argument); + + // Make sure we have the logged in user context + if (!isset($contexts['logged-in-user'])) { + $contexts['logged-in-user'] = ctools_access_get_loggedin_context(); + } + + if (empty($access['plugins'])) { + $access['plugins'] = array(); + } + + $test = ctools_access_new_test($plugin); + + $id = $access['plugins'] ? max(array_keys($access['plugins'])) + 1 : 0; + $access['plugins'][$id] = $test; + + $form_state = array( + 'plugin' => $plugin, + 'id' => $id, + 'test' => &$access['plugins'][$id], + 'access' => &$access, + 'contexts' => $contexts, + 'title' => t('Add criteria'), + 'ajax' => TRUE, + 'modal' => TRUE, + 'modal return' => TRUE, + ); + + $output = ctools_modal_form_wrapper('ctools_access_ajax_edit_item', $form_state); + if (!isset($output[0])) { + $function = $module . '_ctools_access_set'; + if (function_exists($function)) { + $function($argument, $access); + } + + $table = ctools_access_admin_render_table($access, $fragment, $contexts); + $output = array(); + $output[] = ajax_command_replace('table#ctools-access-table', $table); + $output[] = ctools_modal_command_dismiss(); + } + + print ajax_render($output); +} + +/** + * AJAX callback to edit an access test in the list. + */ +function ctools_access_ajax_edit($fragment = NULL, $id = NULL) { + ctools_include('ajax'); + ctools_include('modal'); + ctools_include('context'); + + if (empty($fragment) || !isset($id)) { + ctools_ajax_render_error(); + } + + // Separate the fragment into 'module' and 'argument' + if (strpos($fragment, '-') === FALSE) { + $module = $fragment; + $argument = NULL; + } + else { + list($module, $argument) = explode('-', $fragment, 2); + } + + $function = $module . '_ctools_access_get'; + if (!function_exists($function)) { + ctools_ajax_render_error(t('Missing callback hooks.')); + } + + list($access, $contexts) = $function($argument); + + if (empty($access['plugins'][$id])) { + ctools_ajax_render_error(); + } + + // Make sure we have the logged in user context + if (!isset($contexts['logged-in-user'])) { + $contexts['logged-in-user'] = ctools_access_get_loggedin_context(); + } + + $plugin = ctools_get_access_plugin($access['plugins'][$id]['name']); + $form_state = array( + 'plugin' => $plugin, + 'id' => $id, + 'test' => &$access['plugins'][$id], + 'access' => &$access, + 'contexts' => $contexts, + 'title' => t('Edit criteria'), + 'ajax' => TRUE, + 'ajax' => TRUE, + 'modal' => TRUE, + 'modal return' => TRUE, + ); + + $output = ctools_modal_form_wrapper('ctools_access_ajax_edit_item', $form_state); + if (!isset($output[0])) { + $function = $module . '_ctools_access_set'; + if (function_exists($function)) { + $function($argument, $access); + } + + $table = ctools_access_admin_render_table($access, $fragment, $contexts); + $output = array(); + $output[] = ajax_command_replace('table#ctools-access-table', $table); + $output[] = ctools_modal_command_dismiss(); + } + + print ajax_render($output); +} + +/** + * Form to edit the settings of an access test. + */ +function ctools_access_ajax_edit_item($form, &$form_state) { + $test = &$form_state['test']; + $plugin = &$form_state['plugin']; + if (isset($plugin['required context'])) { + $form['context'] = ctools_context_selector($form_state['contexts'], $plugin['required context'], $test['context']); + } + $form['settings'] = array('#tree' => TRUE); + if ($function = ctools_plugin_get_function($plugin, 'settings form')) { + $form = $function($form, $form_state, $test['settings']); + } + + $form['not'] = array( + '#type' => 'checkbox', + '#title' => t('Reverse (NOT)'), + '#default_value' => !empty($test['not']), + ); + + $form['save'] = array( + '#type' => 'submit', + '#value' => t('Save'), + ); + + return $form; +} + +/** + * Validate handler for argument settings. + */ +function ctools_access_ajax_edit_item_validate($form, &$form_state) { + if ($function = ctools_plugin_get_function($form_state['plugin'], 'settings form validate')) { + $function($form, $form_state); + } +} + +/** + * Submit handler for argument settings. + */ +function ctools_access_ajax_edit_item_submit($form, &$form_state) { + if ($function = ctools_plugin_get_function($form_state['plugin'], 'settings form submit')) { + $function($form, $form_state); + } + + $form_state['test']['settings'] = $form_state['values']['settings']; + if (isset($form_state['values']['context'])) { + $form_state['test']['context'] = $form_state['values']['context']; + } + $form_state['test']['not'] = !empty($form_state['values']['not']); +} + +/** + * AJAX command to remove an access control item. + */ +function ctools_access_ajax_delete($fragment = NULL, $id = NULL) { + ctools_include('ajax'); + ctools_include('modal'); + ctools_include('context'); + + if (empty($fragment) || !isset($id)) { + ajax_render_error(); + } + + // Separate the fragment into 'module' and 'argument' + if (strpos($fragment, '-') === FALSE) { + $module = $fragment; + $argument = NULL; + } + else { + list($module, $argument) = explode('-', $fragment, 2); + } + + $function = $module . '_ctools_access_get'; + if (!function_exists($function)) { + ajax_render_error(t('Missing callback hooks.')); + } + + list($access, $contexts) = $function($argument); + + if (isset($access['plugins'][$id])) { + unset($access['plugins'][$id]); + } + + // re-cache + $function = $module . '_ctools_access_set'; + if (function_exists($function)) { + $function($argument, $access); + } + + $table = ctools_access_admin_render_table($access, $fragment, $contexts); + $output = array(); + $output[] = ajax_command_replace('table#ctools-access-table', $table); + + print ajax_render($output); +} diff --git a/sites/all/modules/contrib/ctools/includes/context-admin.inc b/sites/all/modules/contrib/ctools/includes/context-admin.inc new file mode 100644 index 0000000..821a5b3 --- /dev/null +++ b/sites/all/modules/contrib/ctools/includes/context-admin.inc @@ -0,0 +1,849 @@ + array( + 'title' => t('Arguments'), + 'singular title' => t('argument'), + 'description' => '', // t("Arguments are parsed from the URL and translated into contexts that may be added to the display via the 'content' tab. These arguments are parsed in the order received, and you may use % in your URL to hold the place of an object; the rest of the arguments will come after the URL. For example, if the URL is node/%/panel and your user visits node/1/panel/foo, the first argument will be 1, and the second argument will be foo."), + 'add button' => t('Add argument'), + 'context function' => 'ctools_get_argument', + 'key' => 'arguments', // the key that data will be stored on an object, eg $panel_page + 'sortable' => TRUE, + 'settings' => 'argument_settings', + ), + 'relationship' => array( + 'title' => t('Relationships'), + 'singular title' => t('relationship'), + 'description' => '', // t('Relationships are contexts that are created from already existing contexts; the add relationship button will only appear once there is another context available. Relationships can load objects based upon how they are related to each other; for example, the author of a node, or a taxonomy term attached to a node, or the vocabulary of a taxonomy term.'), + 'add button' => t('Add relationship'), + 'context function' => 'ctools_get_relationship', + 'key' => 'relationships', + 'sortable' => FALSE, + 'settings' => 'relationship_settings', + ), + 'context' => array( + 'title' => t('Contexts'), + 'singular title' => t('context'), + 'description' => '', // t('Contexts are embedded directly into the panel; you generally must select an object in the panel. For example, you could select node 5, or the term "animals" or the user "administrator"'), + 'add button' => t('Add context'), + 'context function' => 'ctools_get_context', + 'key' => 'contexts', + 'sortable' => FALSE, + 'settings' => 'context_settings', + ), + 'requiredcontext' => array( + 'title' => t('Required contexts'), + 'singular title' => t('required context'), + 'description' => '', // t('Required contexts are passed in from some external source, such as a containing panel. If a mini panel has required contexts, it can only appear when that context is available, and therefore will not show up as a standard Drupal block.'), + 'add button' => t('Add required context'), + 'context function' => 'ctools_get_context', + 'key' => 'requiredcontexts', + 'sortable' => FALSE, + ), + ); + } + + if ($type === NULL) { + return $info; + } + + return $info[$type]; +} + + +/** + * Get the data belonging to a particular context. + */ +function ctools_context_get_plugin($type, $name) { + $info = ctools_context_info($type); + if (function_exists($info['context function'])) { + return $info['context function']($name); + } +} + +/** + * Add the argument table plus gadget plus javascript to the form. + */ +function ctools_context_add_argument_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) { + if (empty($cache_key)) { + $cache_key = $object->name; + } + + $form_location = array( + '#prefix' => '', + '#suffix' => '', + '#theme' => 'ctools_context_item_form', + '#cache_key' => $cache_key, + '#ctools_context_type' => 'argument', + '#ctools_context_module' => $module, + ); + + $args = ctools_get_arguments(); + $choices = array(); + foreach ($args as $name => $arg) { + if (empty($arg['no ui'])) { + $choices[$name] = $arg['title']; + } + } + + asort($choices); + + if (!empty($choices) || !empty($object->arguments)) { + ctools_context_add_item_table('argument', $form_location, $choices, $object->arguments); + } +} + +function ctools_context_add_context_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) { + if (empty($cache_key)) { + $cache_key = $object->name; + } + + $form_location = array( + '#prefix' => '', + '#suffix' => '', + '#theme' => 'ctools_context_item_form', + '#cache_key' => $cache_key, + '#ctools_context_type' => 'context', + '#ctools_context_module' => $module, + ); + + // Store the order the choices are in so javascript can manipulate it. + $form_location['markup'] = array( + '#markup' => ' ', + ); + + $choices = array(); + foreach (ctools_get_contexts() as $name => $arg) { + if (empty($arg['no ui'])) { + $choices[$name] = $arg['title']; + } + } + + asort($choices); + + if (!empty($choices) || !empty($object->contexts)) { + ctools_context_add_item_table('context', $form_location, $choices, $object->contexts); + } + +} + +function ctools_context_add_required_context_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) { + if (empty($cache_key)) { + $cache_key = $object->name; + } + + $form_location = array( + '#prefix' => '', + '#suffix' => '', + '#theme' => 'ctools_context_item_form', + '#cache_key' => $cache_key, + '#ctools_context_type' => 'requiredcontext', + '#ctools_context_module' => $module, + ); + + // Store the order the choices are in so javascript can manipulate it. + $form_location['markup'] = array( + '#value' => ' ', + ); + + $choices = array(); + foreach (ctools_get_contexts() as $name => $arg) { + if (empty($arg['no required context ui'])) { + $choices[$name] = $arg['title']; + } + } + + asort($choices); + + if (!empty($choices) || !empty($object->contexts)) { + ctools_context_add_item_table('requiredcontext', $form_location, $choices, $object->requiredcontexts); + } +} + +function ctools_context_add_relationship_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) { + if (empty($cache_key)) { + $cache_key = $object->name; + } + + $form_location = array( + '#prefix' => '', + '#suffix' => '', + '#theme' => 'ctools_context_item_form', + '#cache_key' => $cache_key, + '#ctools_context_type' => 'relationship', + '#ctools_context_module' => $module, + ); + + // Store the order the choices are in so javascript can manipulate it. + $form_location['markup'] = array( + '#value' => ' ', + ); + + $base_contexts = isset($object->base_contexts) ? $object->base_contexts : array(); + $available_relationships = ctools_context_get_relevant_relationships(ctools_context_load_contexts($object, TRUE, $base_contexts)); + + ctools_context_add_item_table('relationship', $form_location, $available_relationships, $object->relationships); +} + +/** + * Include all context administrative include files, css, javascript. + */ +function ctools_context_admin_includes() { + ctools_include('context'); + ctools_include('modal'); + ctools_include('ajax'); + ctools_include('object-cache'); + ctools_modal_add_js(); + ctools_modal_add_plugin_js(ctools_get_contexts()); + ctools_modal_add_plugin_js(ctools_get_relationships()); +} + +/** + * Add the context table to the page. + */ +function ctools_context_add_item_table($type, &$form, $available_contexts, $items) { + $form[$type] = array( + '#tree' => TRUE, + ); + + $module = $form['#ctools_context_module']; + $cache_key = $form['#cache_key']; + + if (isset($items) && is_array($items)) { + foreach ($items as $position => $context) { + ctools_context_add_item_to_form($module, $type, $cache_key, $form[$type][$position], $position, $context); + } + } + + $type_info = ctools_context_info($type); + $form['description'] = array( + '#prefix' => '', + '#suffix' => '', + '#markup' => $type_info['description'], + ); + + ctools_context_add_item_table_buttons($type, $module, $form, $available_contexts); +} + +function ctools_context_add_item_table_buttons($type, $module, &$form, $available_contexts) { + drupal_add_library('system', 'drupal.ajax'); + $form['buttons'] = array( + '#tree' => TRUE, + ); + + if (!empty($available_contexts)) { + $type_info = ctools_context_info($type); + + $module = $form['#ctools_context_module']; + $cache_key = $form['#cache_key']; + + // The URL for this ajax button + $form['buttons'][$type]['add-url'] = array( + '#attributes' => array('class' => array("ctools-$type-add-url")), + '#type' => 'hidden', + '#value' => url("ctools/context/ajax/add/$module/$type/$cache_key", array('absolute' => TRUE)), + ); + + asort($available_contexts); + // This also will be in the URL. + $form['buttons'][$type]['item'] = array( + '#attributes' => array('class' => array("ctools-$type-add-url")), + '#type' => 'select', + '#options' => $available_contexts, + '#required' => FALSE, + ); + + $form['buttons'][$type]['add'] = array( + '#type' => 'submit', + '#attributes' => array('class' => array('ctools-use-modal')), + '#id' => "ctools-$type-add", + '#value' => $type_info['add button'], + ); + } +} + +/** + * Add a row to the form. Used both in the main form and by + * the ajax to add an item. + */ +function ctools_context_add_item_to_form($module, $type, $cache_key, &$form, $position, $item) { + // This is the single function way to load any plugin by variable type. + $info = ctools_context_get_plugin($type, $item['name']); + $form['title'] = array( + '#markup' => check_plain($item['identifier']), + ); + + // Relationships not sortable. + $type_info = ctools_context_info($type); + + if (!empty($type_info['sortable'])) { + $form['position'] = array( + '#type' => 'weight', + '#default_value' => $position, + '#attributes' => array('class' => array('drag-position')), + ); + } + + $form['remove'] = array( + '#markup' => ctools_ajax_image_button(ctools_image_path('icon-delete.png'), "ctools/context/ajax/delete/$module/$type/$cache_key/$position", t('Remove this item.')), + ); + + $form['settings'] = array( + '#markup' => ctools_modal_image_button(ctools_image_path('icon-configure.png'), "ctools/context/ajax/configure/$module/$type/$cache_key/$position", t('Configure settings for this item.')), + ); +} + + +// --------------------------------------------------------------------------- +// AJAX forms and stuff. + +/** + * Ajax entry point to add an context + */ +function ctools_context_ajax_item_add($mechanism = NULL, $type = NULL, $cache_key = NULL, $name = NULL, $step = NULL) { + ctools_include('ajax'); + ctools_include('modal'); + ctools_include('context'); + ctools_include('cache'); + ctools_include('plugins-admin'); + + if (!$name) { + return ctools_ajax_render_error(); + } + + // Load stored object from cache. + if (!($object = ctools_cache_get($mechanism, $cache_key))) { + ctools_ajax_render_error(t('Invalid object name.')); + } + + // Get info about what we're adding, i.e, relationship, context, argument, etc. + $plugin_definition = ctools_context_get_plugin($type, $name); + if (empty($plugin_definition)) { + ctools_ajax_render_error(t('Invalid context type')); + } + + // Set up the $conf array for this plugin + if (empty($step) || empty($object->temporary)) { + // Create the basis for our new context. + $conf = ctools_context_get_defaults($plugin_definition, $object, $type); + $object->temporary = &$conf; + } + else { + $conf = &$object->temporary; + } + + // Load the contexts that may be used. + $base_contexts = isset($object->base_contexts) ? $object->base_contexts : array(); + $contexts = ctools_context_load_contexts($object, TRUE, $base_contexts); + + $type_info = ctools_context_info($type); + $form_state = array( + 'ajax' => TRUE, + 'modal' => TRUE, + 'modal return' => TRUE, + 'object' => &$object, + 'conf' => &$conf, + 'plugin' => $plugin_definition, + 'type' => $type, + 'contexts' => $contexts, + 'title' => t('Add @type "@context"', array('@type' => $type_info['singular title'], '@context' => $plugin_definition['title'])), + 'type info' => $type_info, + 'op' => 'add', + 'step' => $step, + ); + + $form_info = array( + 'path' => "ctools/context/ajax/add/$mechanism/$type/$cache_key/$name/%step", + 'show cancel' => TRUE, + 'default form' => 'ctools_edit_context_form_defaults', + 'auto cache' => TRUE, + 'cache mechanism' => $mechanism, + 'cache key' => $cache_key, + // This is stating what the cache will be referred to in $form_state + 'cache location' => 'object', + ); + + if ($type == 'requiredcontext') { + $form_info += array( + 'add form name' => 'required context add form', + 'edit form name' => 'required context edit form', + ); + } + + $output = ctools_plugin_configure_form($form_info, $form_state); + + if (!empty($form_state['cancel'])) { + $output = array(ctools_modal_command_dismiss()); + } + else if (!empty($form_state['complete'])) { + // Successful submit -- move temporary data to location. + + // Create a reference to the place our context lives. Since this is fairly + // generic, this is the easiest way to get right to the place of the + // object without knowing precisely what data we're poking at. + $ref = &$object->{$type_info['key']}; + + // Figure out the position for our new context. + $position = empty($ref) ? 0 : max(array_keys($ref)) + 1; + + $conf['id'] = ctools_context_next_id($ref, $name); + $ref[$position] = $conf; + + if (isset($object->temporary)) { + unset($object->temporary); + } + + ctools_cache_operation($mechanism, $cache_key, 'finalize', $object); + + // Very irritating way to update the form for our contexts. + $arg_form_state = form_state_defaults() + array( + 'values' => array(), + 'process_input' => FALSE, + 'complete form' => array(), + ); + + $rel_form_state = $arg_form_state; + + $arg_form = array( + '#post' => array(), + '#programmed' => FALSE, + '#tree' => FALSE, + '#parents' => array(), + '#array_parents' => array(), + ); + + // Build a chunk of the form to merge into the displayed form + $arg_form[$type] = array( + '#tree' => TRUE, + ); + $arg_form[$type][$position] = array( + '#tree' => TRUE, + ); + + ctools_context_add_item_to_form($mechanism, $type, $cache_key, $arg_form[$type][$position], $position, $ref[$position]); + $arg_form = form_builder('ctools_context_form', $arg_form, $arg_form_state); + + // Build the relationships table so we can ajax it in. + // This is an additional thing that goes in here. + $rel_form = array( + '#theme' => 'ctools_context_item_form', + '#cache_key' => $cache_key, + '#ctools_context_type' => 'relationship', + '#ctools_context_module' => $mechanism, + '#only_buttons' => TRUE, + '#post' => array(), + '#programmed' => FALSE, + '#tree' => FALSE, + '#parents' => array(), + '#array_parents' => array(), + ); + + $rel_form['relationship'] = array( + '#tree' => TRUE, + ); + + // Allow an object to set some 'base' contexts that come from elsewhere. + $rel_contexts = isset($object->base_contexts) ? $object->base_contexts : array(); + $all_contexts = ctools_context_load_contexts($object, TRUE, $rel_contexts); + $available_relationships = ctools_context_get_relevant_relationships($all_contexts); + + $output = array(); + if (!empty($available_relationships)) { + ctools_context_add_item_table_buttons('relationship', $mechanism, $rel_form, $available_relationships); + $rel_form = form_builder('dummy_form_id', $rel_form, $rel_form_state); + $output[] = ajax_command_replace('div#ctools-relationships-table div.buttons', drupal_render($rel_form)); + } + + $theme_vars = array(); + $theme_vars['type'] = $type; + $theme_vars['form'] = $arg_form[$type][$position]; + $theme_vars['position'] = $position; + $theme_vars['count'] = $position; + $text = theme('ctools_context_item_row', $theme_vars); + $output[] = ajax_command_append('#' . $type . '-table tbody', $text); + $output[] = ajax_command_changed('#' . $type . '-row-' . $position, '.title'); + $output[] = ctools_modal_command_dismiss(); + } + else { + $output = ctools_modal_form_render($form_state, $output); + } + print ajax_render($output); + exit; +} + +/** + * Ajax entry point to edit an item + */ +function ctools_context_ajax_item_edit($mechanism = NULL, $type = NULL, $cache_key = NULL, $position = NULL, $step = NULL) { + ctools_include('ajax'); + ctools_include('modal'); + ctools_include('context'); + ctools_include('cache'); + ctools_include('plugins-admin'); + + if (!isset($position)) { + return ctools_ajax_render_error(); + } + + // Load stored object from cache. + if (!($object = ctools_cache_get($mechanism, $cache_key))) { + ctools_ajax_render_error(t('Invalid object name.')); + } + + $type_info = ctools_context_info($type); + + // Create a reference to the place our context lives. Since this is fairly + // generic, this is the easiest way to get right to the place of the + // object without knowing precisely what data we're poking at. + $ref = &$object->{$type_info['key']}; + + if (empty($step) || empty($object->temporary)) { + // Create the basis for our new context. + $conf = $object->{$type_info['key']}[$position]; + $object->temporary = &$conf; + } + else { + $conf = &$object->temporary; + } + + $name = $ref[$position]['name']; + if (empty($name)) { + ctools_ajax_render_error(); + } + + // load the plugin definition + $plugin_definition = ctools_context_get_plugin($type, $name); + if (empty($plugin_definition)) { + ctools_ajax_render_error(t('Invalid context type')); + } + + // Load the contexts + $base_contexts = isset($object->base_contexts) ? $object->base_contexts : array(); + $contexts = ctools_context_load_contexts($object, TRUE, $base_contexts); + + $form_state = array( + 'ajax' => TRUE, + 'modal' => TRUE, + 'modal return' => TRUE, + 'object' => &$object, + 'conf' => &$conf, + 'position' => $position, + 'plugin' => $plugin_definition, + 'type' => $type, + 'contexts' => $contexts, + 'title' => t('Edit @type "@context"', array('@type' => $type_info['singular title'], '@context' => $plugin_definition['title'])), + 'type info' => $type_info, + 'op' => 'add', + 'step' => $step, + ); + + $form_info = array( + 'path' => "ctools/context/ajax/configure/$mechanism/$type/$cache_key/$position/%step", + 'show cancel' => TRUE, + 'default form' => 'ctools_edit_context_form_defaults', + 'auto cache' => TRUE, + 'cache mechanism' => $mechanism, + 'cache key' => $cache_key, + // This is stating what the cache will be referred to in $form_state + 'cache location' => 'object', + ); + + if ($type == 'requiredcontext') { + $form_info += array( + 'add form name' => 'required context add form', + 'edit form name' => 'required context edit form', + ); + } + + $output = ctools_plugin_configure_form($form_info, $form_state); + + if (!empty($form_state['cancel'])) { + $output = array(ctools_modal_command_dismiss()); + } + else if (!empty($form_state['complete'])) { + // successful submit + $ref[$position] = $conf; + if (isset($object->temporary)) { + unset($object->temporary); + } + + ctools_cache_operation($mechanism, $cache_key, 'finalize', $object); + + $output = array(); + $output[] = ctools_modal_command_dismiss(); + + $arg_form_state = form_state_defaults() + array( + 'values' => array(), + 'process_input' => FALSE, + 'complete form' => array(), + ); + + $arg_form = array( + '#post' => array(), + '#parents' => array(), + '#array_parents' => array(), + '#programmed' => FALSE, + '#tree' => FALSE, + ); + + // Build a chunk of the form to merge into the displayed form + $arg_form[$type] = array( + '#tree' => TRUE, + ); + $arg_form[$type][$position] = array( + '#tree' => TRUE, + ); + + ctools_context_add_item_to_form($mechanism, $type, $cache_key, $arg_form[$type][$position], $position, $ref[$position]); + $arg_form = form_builder('ctools_context_form', $arg_form, $arg_form_state); + + $theme_vars = array(); + $theme_vars['type'] = $type; + $theme_vars['form'] = $arg_form[$type][$position]; + $theme_vars['position'] = $position; + $theme_vars['count'] = $position; + $output[] = ajax_command_replace('#' . $type . '-row-' . $position, theme('ctools_context_item_row', $theme_vars)); + $output[] = ajax_command_changed('#' . $type . '-row-' . $position, '.title'); + } + else { + $output = ctools_modal_form_render($form_state, $output); + } + print ajax_render($output); + exit; +} + +/** + * Get the defaults for a new instance of a context plugin. + * + * @param $plugin_definition + * The metadata definition of the plugin from ctools_get_plugins(). + * @param $object + * The object the context plugin will be added to. + * @param $type + * The type of context plugin. i.e, context, requiredcontext, relationship + */ +function ctools_context_get_defaults($plugin_definition, $object, $type) { + // Fetch the potential id of the plugin so we can append + // title and keyword information for new ones. + $type_info = ctools_context_info($type); + $id = ctools_context_next_id($object->{$type_info['key']}, $plugin_definition['name']); + + $conf = array( + 'identifier' => $plugin_definition['title'] . ($id > 1 ? ' ' . $id : ''), + 'keyword' => ctools_get_keyword($object, $plugin_definition['keyword']), + 'name' => $plugin_definition['name'], + ); + + if (isset($plugin_definition['defaults'])) { + $defaults = $plugin_definition['defaults']; + } + else if (isset($subtype['defaults'])) { + $defaults = $subtype['defaults']; + } + + if (isset($defaults)) { + if (is_string($defaults) && function_exists($defaults)) { + if ($settings = $defaults($plugin_definition)) { + $conf += $settings; + } + } + else if (is_array($defaults)) { + $conf += $defaults; + } + } + + return $conf; +} + +/** + * Form wrapper for the edit context form. + * + * @todo: We should uncombine these. + */ +function ctools_edit_context_form_defaults($form, &$form_state) { + // Basic values required to orient ourselves + $object = $form_state['object']; + $plugin_definition = $form_state['plugin']; + $type_info = $form_state['type info']; + $contexts = $form_state['contexts']; + $conf = $form_state['conf']; + + if ($type_info['key'] == 'arguments' && !isset($conf['default'])) { + $conf['default'] = 'ignore'; + $conf['title'] = ''; + } + + $form['description'] = array( + '#prefix' => '', + '#suffix' => '', + '#markup' => check_plain($plugin_definition['description']), + ); + + if ($type_info['key'] == 'relationships') { + $form['context'] = ctools_context_selector($contexts, $plugin_definition['required context'], isset($conf['context']) ? $conf['context'] : ''); + } + if ($type_info['key'] == 'arguments') { + $form['default'] = array( + '#type' => 'select', + '#title' => t('Default'), + '#options' => array( + 'ignore' => t('Ignore it; content that requires this context will not be available.'), + '404' => t('Display page not found or display nothing at all.'), + ), + '#default_value' => $conf['default'], + '#description' => t('If the argument is missing or is not valid, select how this should behave.'), + ); + + $form['title'] = array( + '#type' => 'textfield', + '#title' => t('Title'), + '#default_value' => $conf['title'], + '#description' => t('Enter a title to use when this argument is present. You may use %KEYWORD substitution, where the keyword is specified below.'), + ); + } + + $form['identifier'] = array( + '#type' => 'textfield', + '#title' => t('Identifier'), + '#description' => t('Enter a name to identify this !type on administrative screens.', array('!type' => t('context'))), + '#default_value' => $conf['identifier'], + ); + + $form['keyword'] = array( + '#type' => 'textfield', + '#title' => t('Keyword'), + '#description' => t('Enter a keyword to use for substitution in titles.'), + '#default_value' => $conf['keyword'], + ); + + if ($type_info['key'] == 'requiredcontexts') { + $form['optional'] = array( + '#type' => 'checkbox', + '#title' => t('Context is optional'), + '#default_value' => !empty($form_state['conf']['optional']), + '#description' => t('This context need not be present for the component to function.'), + ); + } + + $form['#submit'][] = 'ctools_edit_context_form_defaults_submit'; + + return $form; +} + +/** + * Submit handler to store context identifier and keyword info. + */ +function ctools_edit_context_form_defaults_submit(&$form, &$form_state) { + if ($form_state['type info']['key'] == 'relationships') { + $form_state['conf']['context'] = $form_state['values']['context']; + } + if ($form_state['type info']['key'] == 'arguments') { + $form_state['conf']['default'] = $form_state['values']['default']; + $form_state['conf']['title'] = $form_state['values']['title']; + } + if ($form_state['type info']['key'] == 'requiredcontexts') { + $form_state['conf']['optional'] = $form_state['values']['optional']; + } + + $form_state['conf']['identifier'] = $form_state['values']['identifier']; + $form_state['conf']['keyword'] = $form_state['values']['keyword']; +} + +/** + * Ajax entry point to edit an item + */ +function ctools_context_ajax_item_delete($mechanism = NULL, $type = NULL, $cache_key = NULL, $position = NULL) { + ctools_include('ajax'); + ctools_include('context'); + ctools_include('cache'); + + if (!isset($position)) { + return ctools_ajax_render_error(); + } + + // Load stored object from cache. + if (!($object = ctools_cache_get($mechanism, $cache_key))) { + ctools_ajax_render_error(t('Invalid object name.')); + } + + $type_info = ctools_context_info($type); + + // Create a reference to the place our context lives. Since this is fairly + // generic, this is the easiest way to get right to the place of the + // object without knowing precisely what data we're poking at. + $ref = &$object->{$type_info['key']}; + + if (!array_key_exists($position, $ref)) { + ctools_ajax_render_error(t('Unable to delete missing item!')); + } + + unset($ref[$position]); + ctools_cache_operation($mechanism, $cache_key, 'finalize', $object); + + $output = array(); + $output[] = ajax_command_replace('#' . $type . '-row-' . $position, ''); + $output[] = ajax_command_restripe("#$type-table"); + print ajax_render($output); + exit; +} + +// --- End of contexts + +function ctools_save_context($type, &$ref, $form_values) { + $type_info = ctools_context_info($type); + + // Organize arguments + $new = array(); + $order = array(); + + foreach ($ref as $id => $context) { + $position = $form_values[$type][$id]['position']; + $order[$position] = $id; + } + + ksort($order); + foreach ($order as $id) { + $new[] = $ref[$id]; + } + $ref = $new; +} + +function ctools_get_keyword($page, $word) { + // Create a complete set of keywords + $keywords = array(); + foreach (array('arguments', 'relationships', 'contexts', 'requiredcontexts') as $type) { + if (!empty($page->$type) && is_array($page->$type)) { + foreach ($page->$type as $info) { + $keywords[$info['keyword']] = TRUE; + } + } + } + + $keyword = $word; + $count = 1; + while (!empty($keywords[$keyword])) { + $keyword = $word . '_' . ++$count; + } + return $keyword; +} + diff --git a/sites/all/modules/contrib/ctools/includes/context-task-handler.inc b/sites/all/modules/contrib/ctools/includes/context-task-handler.inc new file mode 100644 index 0000000..21ceea5 --- /dev/null +++ b/sites/all/modules/contrib/ctools/includes/context-task-handler.inc @@ -0,0 +1,540 @@ + $handler) { + $plugin = page_manager_get_task_handler($handler->handler); + // First, see if the handler has a tester. + $function = ctools_plugin_get_function($plugin, 'test'); + if ($function) { + $test = $function($handler, $contexts, $args); + if ($test) { + return $id; + } + } + else { + // If not, if it's a 'context' type handler, use the default tester. + if ($plugin['handler type'] == 'context') { + $test = ctools_context_handler_default_test($handler, $contexts, $args); + if ($test) { + return $id; + } + } + } + } + + return FALSE; +} + +/** + * Default test function to see if a task handler should be rendered. + * + * This tests against the standard selection criteria that most task + * handlers should be implementing. + */ +function ctools_context_handler_default_test($handler, $base_contexts, $args) { + ctools_include('context'); + // Add my contexts + $contexts = ctools_context_handler_get_handler_contexts($base_contexts, $handler); + + // Test. + return ctools_context_handler_select($handler, $contexts); +} + +/** + * Render a task handler. + */ +function ctools_context_handler_render_handler($task, $subtask, $handler, $contexts, $args, $page = TRUE) { + $function = page_manager_get_renderer($handler); + if (!$function) { + return NULL; + } + + if ($page) { + if ($subtask) { + $task_name = page_manager_make_task_name($task['name'], $subtask['name']); + } + else { + $task_name = $task['name']; + } + + page_manager_get_current_page(array( + 'name' => $task_name, + 'task' => $task, + 'subtask' => $subtask, + 'contexts' => $contexts, + 'arguments' => $args, + 'handler' => $handler, + )); + } + + $info = $function($handler, $contexts, $args); + if (!$info) { + return NULL; + } + + $context = array( + 'args' => $args, + 'contexts' => $contexts, + 'task' => $task, + 'subtask' => $subtask, + 'handler' => $handler + ); + drupal_alter('ctools_render', $info, $page, $context); + + // If we don't own the page, let the caller deal with rendering. + if (!$page) { + return $info; + } + + if (!empty($info['response code']) && $info['response code'] != 200) { + switch ($info['response code']) { + case 403: + return MENU_ACCESS_DENIED; + case 404: + return MENU_NOT_FOUND; + case 410: + drupal_add_http_header('Status', '410 Gone'); + drupal_exit(); + break; + case 301: + case 302: + case 303: + case 304: + case 305: + case 307: + $info += array( + 'query' => array(), + 'fragment' => '', + ); + $options = array( + 'query' => $info['query'], + 'fragment' => $info['fragment'], + ); + drupal_goto($info['destination'], $options, $info['response code']); + // @todo -- should other response codes be supported here? + } + } + + $plugin = page_manager_get_task_handler($handler->handler); + + if (module_exists('contextual') && user_access('access contextual links') && isset($handler->task)) { + // Provide a contextual link to edit this, if we can: + $callback = isset($plugin['contextual link']) ? $plugin['contextual link'] : 'ctools_task_handler_default_contextual_link'; + if ($callback && function_exists($callback)) { + $links = $callback($handler, $plugin, $contexts, $args); + } + + if (!empty($links) && is_array($links)) { + $build = array( + '#theme_wrappers' => array('container'), + '#attributes' => array('class' => array('contextual-links-region')), + ); + + if (!is_array($info['content'])) { + $build['content']['#markup'] = $info['content']; + } + else { + $build['content'] = $info['content']; + } + + $build['contextual_links'] = array( + '#prefix' => '', + '#suffix' => '', + '#theme' => 'links__contextual', + '#links' => $links, + '#attributes' => array('class' => array('contextual-links')), + '#attached' => array( + 'library' => array(array('contextual', 'contextual-links')), + ), + ); + $info['content'] = $build; + } + } + + foreach (ctools_context_handler_get_task_arguments($task, $subtask) as $id => $argument) { + $plugin = ctools_get_argument($argument['name']); + $cid = ctools_context_id($argument, 'argument'); + if (!empty($contexts[$cid]) && ($function = ctools_plugin_get_function($plugin, 'breadcrumb'))) { + $function($argument['settings'], $contexts[$cid]); + } + } + + if (isset($info['title'])) { + drupal_set_title($info['title'], PASS_THROUGH); + } + + // Only directly output if $page was set to true. + if (!empty($info['no_blocks'])) { + ctools_set_no_blocks(FALSE); + } + return $info['content']; +} + +/** + * Default function to provide contextual link for a task as defined by the handler. + * + * This provides a simple link to th main content operation and is suitable + * for most normal handlers. Setting 'contextual link' to a function overrides + * this and setting it to FALSE will prevent a contextual link from appearing. + */ +function ctools_task_handler_default_contextual_link($handler, $plugin, $contexts, $args) { + if (!user_access('administer page manager')) { + return; + } + + $task = page_manager_get_task($handler->task); + + $title = !empty($task['tab title']) ? $task['tab title'] : t('Edit @type', array('@type' => $plugin['title'])); + $trail = array(); + if (!empty($plugin['tab operation'])) { + if (is_array($plugin['tab operation'])) { + $trail = $plugin['tab operation']; + } + else if (function_exists($plugin['tab operation'])) { + $trail = $plugin['tab operation']($handler, $contexts, $args); + } + } + $path = page_manager_edit_url(page_manager_make_task_name($handler->task, $handler->subtask), $trail); + + $links = array(array( + 'href' => $path, + 'title' => $title, + 'query' => drupal_get_destination(), + )); + + return $links; +} + +/** + * Called to execute actions that should happen before a handler is rendered. + */ +function ctools_context_handler_pre_render($handler, $contexts, $args) { } + +/** + * Compare arguments to contexts for selection purposes. + * + * @param $handler + * The handler in question. + * @param $contexts + * The context objects provided by the task. + * + * @return + * TRUE if these contexts match the selection rules. NULL or FALSE + * otherwise. + */ +function ctools_context_handler_select($handler, $contexts) { + if (empty($handler->conf['access'])) { + return TRUE; + } + + ctools_include('context'); + return ctools_access($handler->conf['access'], $contexts); +} + +/** + * Get the array of summary strings for the arguments. + * + * These summary strings are used to communicate to the user what + * arguments the task handlers are selecting. + * + * @param $task + * The loaded task plugin. + * @param $subtask + * The subtask id. + * @param $handler + * The handler to be checked. + */ +function ctools_context_handler_summary($task, $subtask, $handler) { + if (empty($handler->conf['access']['plugins'])) { + return array(); + } + + ctools_include('context'); + $strings = array(); + $contexts = ctools_context_handler_get_all_contexts($task, $subtask, $handler); + + foreach ($handler->conf['access']['plugins'] as $test) { + $plugin = ctools_get_access_plugin($test['name']); + if ($string = ctools_access_summary($plugin, $contexts, $test)) { + $strings[] = $string; + } + } + + return $strings; +} + +// -------------------------------------------------------------------------- +// Tasks and Task handlers can both have their own sources of contexts. +// Sometimes we need all of these contexts at once (when editing +// the task handler, for example) but sometimes we need them separately +// (when a task has contexts loaded and is trying out the task handlers, +// for example). Therefore there are two paths we can take to getting contexts. + +/** + * Load the contexts for a task, using arguments. + * + * This creates the base array of contexts, loaded from arguments, suitable + * for use in rendering. + */ +function ctools_context_handler_get_task_contexts($task, $subtask, $args) { + $contexts = ctools_context_handler_get_base_contexts($task, $subtask); + $arguments = ctools_context_handler_get_task_arguments($task, $subtask); + ctools_context_get_context_from_arguments($arguments, $contexts, $args); + + return $contexts; +} + +/** + * Load the contexts for a task handler. + * + * This expands a base set of contexts passed in from a task with the + * contexts defined on the task handler. The contexts from the task + * must already have been loaded. + */ +function ctools_context_handler_get_handler_contexts($contexts, $handler) { + $object = ctools_context_handler_get_handler_object($handler); + return ctools_context_load_contexts($object, FALSE, $contexts); +} + +/** + * Load the contexts for a task and task handler together. + * + * This pulls the arguments from a task and everything else from a task + * handler and loads them as a group. Since there is no data, this loads + * the contexts as placeholders. + */ +function ctools_context_handler_get_all_contexts($task, $subtask, $handler) { + $contexts = array(); + + $object = ctools_context_handler_get_task_object($task, $subtask, $handler); + $contexts = ctools_context_load_contexts($object, TRUE, $contexts); + ctools_context_handler_set_access_restrictions($task, $subtask, $handler, $contexts); + return $contexts; +} + +/** + * Create an object suitable for use with the context system that kind of + * expects things in a certain, kind of clunky format. + */ +function ctools_context_handler_get_handler_object($handler) { + $object = new stdClass; + $object->name = $handler->name; + $object->contexts = isset($handler->conf['contexts']) ? $handler->conf['contexts'] : array(); + $object->relationships = isset($handler->conf['relationships']) ? $handler->conf['relationships'] : array(); + + return $object; +} + +/** + * Create an object suitable for use with the context system that kind of + * expects things in a certain, kind of clunky format. This one adds in + * arguments from the task. + */ +function ctools_context_handler_get_task_object($task, $subtask, $handler) { + $object = new stdClass; + $object->name = !empty($handler->name) ? $handler->name : 'temp'; + $object->base_contexts = ctools_context_handler_get_base_contexts($task, $subtask, TRUE); + $object->arguments = ctools_context_handler_get_task_arguments($task, $subtask); + $object->contexts = isset($handler->conf['contexts']) ? $handler->conf['contexts'] : array(); + $object->relationships = isset($handler->conf['relationships']) ? $handler->conf['relationships'] : array(); + + return $object; +} + +/** + * Get base contexts from a task, if it has any. + * + * Tasks can get their contexts either from base contexts or arguments; base + * contexts extract their information from the environment. + */ +function ctools_context_handler_get_base_contexts($task, $subtask, $placeholders = FALSE) { + if ($function = ctools_plugin_get_function($task, 'get base contexts')) { + return $function($task, $subtask, $placeholders); + } + + return array(); +} + +/** + * Get the arguments from a task that are used to load contexts. + */ +function ctools_context_handler_get_task_arguments($task, $subtask) { + if ($function = ctools_plugin_get_function($task, 'get arguments')) { + return $function($task, $subtask); + } + + return array(); +} + +/** + * Set any access restrictions on the contexts for a handler. + * + * Both the task and the handler could add restrictions to the contexts + * based upon the access control. These restrictions might be useful + * to limit what kind of content appears in the add content dialog; + * for example, if we have an access item that limits a node context + * to only 'story' and 'page' types, there is no need for content that + * only applies to the 'poll' type to appear. + */ +function ctools_context_handler_set_access_restrictions($task, $subtask, $handler, &$contexts) { + // First, for the task: + if ($function = ctools_plugin_get_function($task, 'access restrictions')) { + $function($task, $subtask, $contexts); + } + + // Then for the handler: + if (isset($handler->conf['access'])) { + ctools_access_add_restrictions($handler->conf['access'], $contexts); + } +} + +/** + * Form to choose context based selection rules for a task handler. + * + * The configuration will be assumed to go simply in $handler->conf and + * will be keyed by the argument ID. + */ +function ctools_context_handler_edit_criteria($form, &$form_state) { + if (!isset($form_state['handler']->conf['access'])) { + $form_state['handler']->conf['access'] = array(); + } + + ctools_include('context'); + ctools_include('modal'); + ctools_include('ajax'); + ctools_modal_add_plugin_js(ctools_get_access_plugins()); + ctools_include('context-access-admin'); + $form_state['module'] = (isset($form_state['module'])) ? $form_state['module'] : 'page_manager_task_handler'; + // Encode a bunch of info into the argument so we can get our cache later + $form_state['callback argument'] = $form_state['task_name'] . '*' . $form_state['handler']->name; + $form_state['access'] = $form_state['handler']->conf['access']; + $form_state['no buttons'] = TRUE; + $form_state['contexts'] = ctools_context_handler_get_all_contexts($form_state['task'], $form_state['subtask'], $form_state['handler']); + + $form['markup'] = array( + '#markup' => '' . + t('If there is more than one variant on a page, when the page is visited each variant is given an opportunity to be displayed. Starting from the first variant and working to the last, each one tests to see if its selection rules will pass. The first variant that meets its criteria (as specified below) will be used.') . + '', + ); + $form = ctools_access_admin_form($form, $form_state); + return $form; +} + +/** + * Submit handler for rules selection + */ +function ctools_context_handler_edit_criteria_submit(&$form, &$form_state) { + $form_state['handler']->conf['access']['logic'] = $form_state['values']['logic']; +} + +/** + * Edit contexts that go with this panel. + */ +function ctools_context_handler_edit_context($form, &$form_state) { + ctools_include('context-admin'); + ctools_context_admin_includes(); + + $handler = $form_state['handler']; + $page = $form_state['page']; + $cache_name = $handler->name ? $handler->name : 'temp'; + if (isset($page->context_cache[$cache_name])) { + $cache = $page->context_cache[$cache_name]; + } + else { + $cache = ctools_context_handler_get_task_object($form_state['task'], $form_state['subtask'], $form_state['handler']); + $form_state['page']->context_cache[$cache_name] = $cache; + } + + $form['right'] = array( + '#prefix' => '', + ); + + $module = 'page_manager_context::' . $page->task_name; + ctools_context_add_context_form($module, $form, $form_state, $form['right']['contexts_table'], $cache); + ctools_context_add_relationship_form($module, $form, $form_state, $form['right']['relationships_table'], $cache); + + $theme_vars = array(); + $theme_vars['object'] = $cache; + $theme_vars['header'] = t('Summary of contexts'); + $form['left']['summary'] = array( + '#prefix' => '', + '#suffix' => '', + ); + + $form['left'] = array( + '#prefix' => '', + '#suffix' => '', + '#suffix' => '', + '#markup' => theme('ctools_context_list', $theme_vars), + ); + + $form_state['context_object'] = &$cache; + return $form; +} + +/** + * Process submission of the context edit form. + */ +function ctools_context_handler_edit_context_submit(&$form, &$form_state) { + $handler = &$form_state['handler']; + + $cache_name = $handler->name ? $handler->name : 'temp'; + + $handler->conf['contexts'] = $form_state['context_object']->contexts; + $handler->conf['relationships'] = $form_state['context_object']->relationships; + if (isset($form_state['page']->context_cache[$cache_name])) { + unset($form_state['page']->context_cache[$cache_name]); + } +} + diff --git a/sites/all/modules/contrib/ctools/includes/context.inc b/sites/all/modules/contrib/ctools/includes/context.inc new file mode 100644 index 0000000..1f9c1e4 --- /dev/null +++ b/sites/all/modules/contrib/ctools/includes/context.inc @@ -0,0 +1,1602 @@ +type = $type; + $this->data = $data; + $this->title = t('Unknown context'); + } + + function is_type($type) { + if ($type == 'any' || $this->type == 'any') { + return TRUE; + } + + $a = is_array($type) ? $type : array($type); + $b = is_array($this->type) ? $this->type : array($this->type); + return (bool) array_intersect($a, $b); + } + + function get_argument() { + return $this->argument; + } + + function get_original_argument() { + if (!is_null($this->original_argument)) { + return $this->original_argument; + } + return $this->argument; + } + + function get_keyword() { + return $this->keyword; + } + + function get_identifier() { + return $this->identifier; + } + + function get_title() { + return $this->title; + } + + function get_page_title() { + return $this->page_title; + } +} + +/** + * Used to create a method of comparing if a list of contexts + * match a required context type. + */ +class ctools_context_required { + var $keywords = ''; + + /** + * If set, the title will be used in the selector to identify + * the context. This is very useful when multiple contexts + * are required to inform the user will be used for what. + */ + var $title = NULL; + + /** + * Test to see if this context is required. + */ + var $required = TRUE; + + /** + * If TRUE, skip the check in ctools_context_required::select() + * for contexts whose names may have changed. + */ + var $skip_name_check = FALSE; + + /** + * + * @param $title + * The first parameter should be the 'title' of the context for use + * in UYI selectors when multiple contexts qualify. + * @param ... + * One or more keywords to use for matching which contexts are allowed. + */ + function ctools_context_required($title) { + $args = func_get_args(); + $this->title = array_shift($args); + + // If we have a boolean value at the end for $skip_name_check, store it + if (is_bool(end($args))) { + $this->skip_name_check = array_pop($args); + } + + // If we were given restrictions at the end, store them. + if (count($args) > 1 && is_array(end($args))) { + $this->restrictions = array_pop($args); + } + + if (count($args) == 1) { + $args = array_shift($args); + } + $this->keywords = $args; + } + + function filter($contexts) { + $result = array(); + + // See which of these contexts are valid + foreach ((array) $contexts as $cid => $context) { + if ($context->is_type($this->keywords)) { + // Compare to see if our contexts were met. + if (!empty($this->restrictions) && !empty($context->restrictions)) { + foreach ($this->restrictions as $key => $values) { + // If we have a restriction, the context must either not have that + // restriction listed, which means we simply don't know what it is, + // or there must be an intersection of the restricted values on + // both sides. + if (!is_array($values)) { + $values = array($values); + } + if (!empty($context->restrictions[$key]) && !array_intersect($values, $context->restrictions[$key])) { + continue 2; + } + } + } + $result[$cid] = $context; + } + } + + return $result; + } + + function select($contexts, $context) { + if (!is_array($contexts)) { + if (is_object($contexts) && $contexts instanceof ctools_context) { + $contexts = array($contexts->id => $contexts); + } + else { + $contexts = array($contexts); + } + } + + // If we had requested a $context but that $context doesn't exist + // in our context list, there is a good chance that what happened + // is our context IDs changed. See if there's another context + // that satisfies our requirements. + if (!$this->skip_name_check && !empty($context) && !isset($contexts[$context])) { + $choices = $this->filter($contexts); + + // If we got a hit, take the first one that matches. + if ($choices) { + $keys = array_keys($choices); + $context = reset($keys); + } + } + + if (empty($context) || empty($contexts[$context])) { + return FALSE; + } + return $contexts[$context]; + } +} + +/** + * Used to compare to see if a list of contexts match an optional context. This + * can produce empty contexts to use as placeholders. + */ +class ctools_context_optional extends ctools_context_required { + var $required = FALSE; + function ctools_context_optional() { + $args = func_get_args(); + call_user_func_array(array($this, 'ctools_context_required'), $args); + } + + /** + * Add the 'empty' context which is possible for optional + */ + function add_empty(&$contexts) { + $context = new ctools_context('any'); + $context->title = t('No context'); + $context->identifier = t('No context'); + $contexts['empty'] = $context; + } + + function filter($contexts) { + $this->add_empty($contexts); + return parent::filter($contexts); + } + + function select($contexts, $context) { + $this->add_empty($contexts); + if (empty($context)) { + return $contexts['empty']; + } + + $result = parent::select($contexts, $context); + + // Don't flip out if it can't find the context; this is optional, put + // in an empty. + if ($result == FALSE) { + $result = $contexts['empty']; + } + return $result; + } +} + +/** + * Return a keyed array of context that match the given 'required context' + * filters. + * + * Functions or systems that require contexts of a particular type provide a + * ctools_context_required or ctools_context_optional object. This function + * examines that object and an array of contexts to determine which contexts + * match the filter. + * + * Since multiple contexts can be required, this function will accept either + * an array of all required contexts, or just a single required context object. + * + * @param $contexts + * A keyed array of all available contexts. + * @param $required + * A ctools_context_required or ctools_context_optional object, or an array + * of such objects. + * + * @return + * A keyed array of contexts that match the filter. + */ +function ctools_context_filter($contexts, $required) { + if (is_array($required)) { + $result = array(); + foreach ($required as $r) { + $result = array_merge($result, _ctools_context_filter($contexts, $r)); + } + return $result; + } + + return _ctools_context_filter($contexts, $required); +} + +function _ctools_context_filter($contexts, $required) { + $result = array(); + + if (is_object($required)) { + $result = $required->filter($contexts); + } + + return $result; +} + +/** + * Create a select box to choose possible contexts. + * + * This only creates a selector if there is actually a choice; if there + * is only one possible context, that one is silently assigned. + * + * If an array of required contexts is provided, one selector will be + * provided for each context. + * + * @param $contexts + * A keyed array of all available contexts. + * @param $required + * The required context object or array of objects. + * + * @return + * A form element, or NULL if there are no contexts that satisfy the + * requirements. + */ +function ctools_context_selector($contexts, $required, $default) { + if (is_array($required)) { + $result = array('#tree' => TRUE); + $count = 1; + foreach ($required as $id => $r) { + $result[] = _ctools_context_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++); + } + return $result; + } + + return _ctools_context_selector($contexts, $required, $default); +} + +function _ctools_context_selector($contexts, $required, $default, $num = 0) { + $filtered = ctools_context_filter($contexts, $required); + $count = count($filtered); + + $form = array(); + + if ($count >= 1) { + // If there's more than one to choose from, create a select widget. + foreach ($filtered as $cid => $context) { + $options[$cid] = $context->get_identifier(); + } + if (!empty($required->title)) { + $title = $required->title; + } + else { + $title = $num ? t('Context %count', array('%count' => $num)) : t('Context'); + } + + $form = array( + '#type' => 'select', + '#options' => $options, + '#title' => $title, + '#default_value' => $default, + ); + } + + return $form; +} + +/** + * Are there enough contexts for a plugin? + * + * Some plugins can have a 'required contexts' item which can either + * be a context requirement object or an array of them. When contexts + * are required, items that do not have enough contexts should not + * appear. This tests an item to see if it has enough contexts + * to actually appear. + * + * @param $contexts + * A keyed array of all available contexts. + * @param $required + * The required context object or array of objects. + * + * @return + * TRUE if there are enough contexts, FALSE if there are not. + */ +function ctools_context_match_requirements($contexts, $required) { + if (!is_array($required)) { + $required = array($required); + } + + // Get the keys to avoid bugs in PHP 5.0.8 with keys and loops. + // And use it to remove optional contexts. + $keys = array_keys($required); + foreach ($keys as $key) { + if (empty($required[$key]->required)) { + unset($required[$key]); + } + } + + $count = count($required); + return (count(ctools_context_filter($contexts, $required)) >= $count); +} + +/** + * Create a select box to choose possible contexts. + * + * This only creates a selector if there is actually a choice; if there + * is only one possible context, that one is silently assigned. + * + * If an array of required contexts is provided, one selector will be + * provided for each context. + * + * @param $contexts + * A keyed array of all available contexts. + * @param $required + * The required context object or array of objects. + * + * @return + * A form element, or NULL if there are no contexts that satisfy the + * requirements. + */ +function ctools_context_converter_selector($contexts, $required, $default) { + if (is_array($required)) { + $result = array('#tree' => TRUE); + $count = 1; + foreach ($required as $id => $r) { + $result[] = _ctools_context_converter_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++); + } + return $result; + } + + return _ctools_context_converter_selector($contexts, $required, $default); +} + +function _ctools_context_converter_selector($contexts, $required, $default, $num = 0) { + $filtered = ctools_context_filter($contexts, $required); + $count = count($filtered); + + $form = array(); + + if ($count > 1) { + // If there's more than one to choose from, create a select widget. + $options = array(); + foreach ($filtered as $cid => $context) { + if ($context->type == 'any') { + $options[''] = t('No context'); + continue; + } + $key = $context->get_identifier(); + if ($converters = ctools_context_get_converters($cid . '.', $context)) { + $options[$key] = $converters; + } + } + if (empty($options)) { + return array( + '#type' => 'value', + '#value' => 'any', + ); + } + if (!empty($required->title)) { + $title = $required->title; + } + else { + $title = $num ? t('Context %count', array('%count' => $num)) : t('Context'); + } + + return array( + '#type' => 'select', + '#options' => $options, + '#title' => $title, + '#description' => t('Please choose which context and how you would like it converted.'), + '#default_value' => $default, + ); + } +} + +/** + * Get a list of converters available for a given context. + */ +function ctools_context_get_converters($cid, $context) { + if (empty($context->plugin)) { + return array(); + } + + return _ctools_context_get_converters($cid, $context->plugin); +} + +/** + * Get a list of converters available for a given context. + */ +function _ctools_context_get_converters($id, $plugin_name) { + $plugin = ctools_get_context($plugin_name); + if (empty($plugin['convert list'])) { + return array(); + } + + $converters = array(); + if (is_array($plugin['convert list'])) { + $converters = $plugin['convert list']; + } + else if ($function = ctools_plugin_get_function($plugin, 'convert list')) { + $converters = (array) $function($plugin); + } + + foreach (module_implements('ctools_context_convert_list_alter') as $module) { + $function = $module . '_ctools_context_convert_list_alter'; + $function($plugin, $converters); + } + + // Now, change them all to include the plugin: + $return = array(); + foreach ($converters as $key => $title) { + $return[$id . $key] = $title; + } + + natcasesort($return); + return $return; +} + +/** + * Get a list of all contexts + converters available. + */ +function ctools_context_get_all_converters() { + $contexts = ctools_get_contexts(); + $converters = array(); + foreach ($contexts as $name => $context) { + if (empty($context['no required context ui'])) { + $context_converters = _ctools_context_get_converters($name . '.', $name); + if ($context_converters) { + $converters[$context['title']] = $context_converters; + } + } + } + + return $converters; +} + +/** + * Let the context convert an argument based upon the converter that was given. + * + * @param $context + * The context object + * @param $converter + * The converter to use, which should be a string provided by the converter list. + * @param $converter_options + * A n array of options to pass on to the generation function. For contexts + * that use token module, of particular use is 'sanitize' => FALSE which can + * get raw tokens. This should ONLY be used in values that will later be + * treated as unsafe user input since these values are by themselves unsafe. + * It is particularly useful to get raw values from Field API. + */ +function ctools_context_convert_context($context, $converter, $converter_options = array()) { + // Contexts without plugins might be optional placeholders. + if (empty($context->plugin)) { + return; + } + + $value = $context->argument; + $plugin = ctools_get_context($context->plugin); + if ($function = ctools_plugin_get_function($plugin, 'convert')) { + $value = $function($context, $converter, $converter_options); + } + + foreach (module_implements('ctools_context_converter_alter') as $module) { + $function = $module . '_ctools_context_converter_alter'; + $function($context, $converter, $value, $converter_options); + } + + return $value; +} + +/** + * Choose a context or contexts based upon the selection made via + * ctools_context_filter. + * + * @param $contexts + * A keyed array of all available contexts + * @param $required + * The required context object provided by the plugin + * @param $context + * The selection made using ctools_context_selector + */ +function ctools_context_select($contexts, $required, $context) { + if (is_array($required)) { + $result = array(); + foreach ($required as $id => $r) { + if (empty($required[$id])) { + continue; + } + + if (($result[] = _ctools_context_select($contexts, $r, $context[$id])) === FALSE) { + return FALSE; + } + } + return $result; + } + + return _ctools_context_select($contexts, $required, $context); +} + +function _ctools_context_select($contexts, $required, $context) { + if (!is_object($required)) { + return FALSE; + } + + return $required->select($contexts, $context); +} + +/** + * Create a new context object. + * + * @param $type + * The type of context to create; this loads a plugin. + * @param $data + * The data to put into the context. + * @param $empty + * Whether or not this context is specifically empty. + * @param $conf + * A configuration structure if this context was created via UI. + * + * @return + * A $context or NULL if one could not be created. + */ +function ctools_context_create($type, $data = NULL, $conf = FALSE) { + ctools_include('plugins'); + $plugin = ctools_get_context($type); + + if ($function = ctools_plugin_get_function($plugin, 'context')) { + return $function(FALSE, $data, $conf, $plugin); + } +} + +/** + * Create an empty context object. + * + * Empty context objects are primarily used as placeholders in the UI where + * the actual contents of a context object may not be known. It may have + * additional text embedded to give the user clues as to how the context + * is used. + * + * @param $type + * The type of context to create; this loads a plugin. + * + * @return + * A $context or NULL if one could not be created. + */ +function ctools_context_create_empty($type) { + $plugin = ctools_get_context($type); + if ($function = ctools_plugin_get_function($plugin, 'context')) { + $context = $function(TRUE, NULL, FALSE, $plugin); + if (is_object($context)) { + $context->empty = TRUE; + } + + return $context; + } +} + +/** + * Perform keyword and context substitutions. + */ +function ctools_context_keyword_substitute($string, $keywords, $contexts, $converter_options = array()) { + // Ensure a default keyword exists: + $keywords['%%'] = '%'; + + // Match contexts to the base keywords: + $context_keywords = array(); + foreach ($contexts as $context) { + if (isset($context->keyword)) { + $context_keywords[$context->keyword] = $context; + } + } + + // Look for context matches we we only have to convert known matches. + $matches = array(); + if (preg_match_all('/%(%|[a-zA-Z0-9_-]+(?:\:[a-zA-Z0-9_-]+)*)/us', $string, $matches)) { + foreach ($matches[1] as $keyword) { + // Ignore anything it finds with %%. + if ($keyword[0] == '%') { + continue; + } + + // If the keyword is already set by something passed in, don't try to + // overwrite it. + if (!empty($keywords['%' . $keyword])) { + continue; + } + + // Figure out our keyword and converter, if specified. + if (strpos($keyword, ':')) { + list($context, $converter) = explode(':', $keyword, 2); + } + else { + $context = $keyword; + if (isset($context_keywords[$keyword])) { + $plugin = ctools_get_context($context_keywords[$context]->plugin); + + // Fall back to a default converter, if specified. + if ($plugin && !empty($plugin['convert default'])) { + $converter = $plugin['convert default']; + } + } + } + + if (empty($context_keywords[$context]) || !empty($context_keywords[$context]->empty)) { + $keywords['%' . $keyword] = ''; + } + else if (!empty($converter)) { + $keywords['%' . $keyword] = ctools_context_convert_context($context_keywords[$context], $converter, $converter_options); + } + else { + $keywords['%' . $keyword] = $context_keywords[$keyword]->title; + } + } + } + return strtr($string, $keywords); +} + +/** + * Determine a unique context ID for a context + * + * Often contexts of many different types will be placed into a list. This + * ensures that even though contexts of multiple types may share IDs, they + * are unique in the final list. + */ +function ctools_context_id($context, $type = 'context') { + if (!$context['id']) { + $context['id'] = 1; + } + + return $type . '_' . $context['name'] . '_' . $context['id']; +} + +/** + * Get the next id available given a list of already existing objects. + * + * This finds the next id available for the named object. + * + * @param $objects + * A list of context descriptor objects, i.e, arguments, relationships, contexts, etc. + * @param $name + * The name being used. + */ +function ctools_context_next_id($objects, $name) { + $id = 0; + // Figure out which instance of this argument we're creating + if (!$objects) { + return $id + 1; + } + + foreach ($objects as $object) { + if (isset($object['name']) && $object['name'] == $name) { + if ($object['id'] > $id) { + $id = $object['id']; + } + } + } + + return $id + 1; +} + + +// --------------------------------------------------------------------------- +// Functions related to contexts from arguments. + +/** + * Fetch metadata on a specific argument plugin. + * + * @param $argument + * Name of an argument plugin. + * + * @return + * An array with information about the requested argument plugin. + */ +function ctools_get_argument($argument) { + ctools_include('plugins'); + return ctools_get_plugins('ctools', 'arguments', $argument); +} + +/** + * Fetch metadata for all argument plugins. + * + * @return + * An array of arrays with information about all available argument plugins. + */ +function ctools_get_arguments() { + ctools_include('plugins'); + return ctools_get_plugins('ctools', 'arguments'); +} + +/** + * Get a context from an argument. + * + * @param $argument + * The configuration of an argument. It must contain the following data: + * - name: The name of the argument plugin being used. + * - argument_settings: The configuration based upon the plugin forms. + * - identifier: The human readable identifier for this argument, usually + * defined by the UI. + * - keyword: The keyword used for this argument for substitutions. + * + * @param $arg + * The actual argument received. This is expected to be a string from a URL but + * this does not have to be the only source of arguments. + * @param $empty + * If true, the $arg will not be used to load the context. Instead, an empty + * placeholder context will be loaded. + * + * @return + * A context object if one can be loaded. + */ +function ctools_context_get_context_from_argument($argument, $arg, $empty = FALSE) { + ctools_include('plugins'); + if (empty($argument['name'])) { + return; + } + + if ($function = ctools_plugin_load_function('ctools', 'arguments', $argument['name'], 'context')) { + // Backward compatibility: Merge old style settings into new style: + if (!empty($argument['settings'])) { + $argument += $argument['settings']; + unset($argument['settings']); + } + + $context = $function($arg, $argument, $empty); + + if (is_object($context)) { + $context->identifier = $argument['identifier']; + $context->page_title = isset($argument['title']) ? $argument['title'] : ''; + $context->keyword = $argument['keyword']; + $context->id = ctools_context_id($argument, 'argument'); + $context->original_argument = $arg; + + if (!empty($context->empty)) { + $context->placeholder = array( + 'type' => 'argument', + 'conf' => $argument, + ); + } + } + return $context; + } +} + +/** + * Retrieve a list of empty contexts for all arguments. + */ +function ctools_context_get_placeholders_from_argument($arguments) { + $contexts = array(); + foreach ($arguments as $argument) { + $context = ctools_context_get_context_from_argument($argument, NULL, TRUE); + if ($context) { + $contexts[ctools_context_id($argument, 'argument')] = $context; + } + } + return $contexts; +} + +/** + * Load the contexts for a given list of arguments. + * + * @param $arguments + * The array of argument definitions. + * @param &$contexts + * The array of existing contexts. New contexts will be added to this array. + * @param $args + * The arguments to load. + * + * @return + * FALSE if an argument wants to 404. + */ +function ctools_context_get_context_from_arguments($arguments, &$contexts, $args) { + foreach ($arguments as $argument) { + // pull the argument off the list. + $arg = array_shift($args); + $id = ctools_context_id($argument, 'argument'); + + // For % arguments embedded in the URL, our context is already loaded. + // There is no need to go and load it again. + if (empty($contexts[$id])) { + if ($context = ctools_context_get_context_from_argument($argument, $arg)) { + $contexts[$id] = $context; + } + } + else { + $context = $contexts[$id]; + } + + if ((empty($context) || empty($context->data)) && !empty($argument['default']) && $argument['default'] == '404') { + return FALSE; + } + } + return TRUE; +} + +// --------------------------------------------------------------------------- +// Functions related to contexts from relationships. + +/** + * Fetch metadata on a specific relationship plugin. + * + * @param $content type + * Name of a panel content type. + * + * @return + * An array with information about the requested relationship. + */ +function ctools_get_relationship($relationship) { + ctools_include('plugins'); + return ctools_get_plugins('ctools', 'relationships', $relationship); +} + +/** + * Fetch metadata for all relationship plugins. + * + * @return + * An array of arrays with information about all available relationships. + */ +function ctools_get_relationships() { + ctools_include('plugins'); + return ctools_get_plugins('ctools', 'relationships'); +} + +/** + * + * @param $relationship + * The configuration of a relationship. It must contain the following data: + * - name: The name of the relationship plugin being used. + * - relationship_settings: The configuration based upon the plugin forms. + * - identifier: The human readable identifier for this relationship, usually + * defined by the UI. + * - keyword: The keyword used for this relationship for substitutions. + * + * @param $source_context + * The context this relationship is based upon. + * + * @param $placeholders + * If TRUE, placeholders are acceptable. + * + * @return + * A context object if one can be loaded. + */ +function ctools_context_get_context_from_relationship($relationship, $source_context, $placeholders = FALSE) { + ctools_include('plugins'); + if ($function = ctools_plugin_load_function('ctools', 'relationships', $relationship['name'], 'context')) { + // Backward compatibility: Merge old style settings into new style: + if (!empty($relationship['relationship_settings'])) { + $relationship += $relationship['relationship_settings']; + unset($relationship['relationship_settings']); + } + + $context = $function($source_context, $relationship, $placeholders); + if ($context) { + $context->identifier = $relationship['identifier']; + $context->page_title = isset($relationship['title']) ? $relationship['title'] : ''; + $context->keyword = $relationship['keyword']; + if (!empty($context->empty)) { + $context->placeholder = array( + 'type' => 'relationship', + 'conf' => $relationship, + ); + } + return $context; + } + } +} + +/** + * Fetch all relevant relationships. + * + * Relevant relationships are any relationship that can be created based upon + * the list of existing contexts. For example, the 'node author' relationship + * is relevant if there is a 'node' context, but makes no sense if there is + * not one. + * + * @param $contexts + * An array of contexts used to figure out which relationships are relevant. + * + * @return + * An array of relationship keys that are relevant for the given set of + * contexts. + */ +function ctools_context_get_relevant_relationships($contexts) { + $relevant = array(); + $relationships = ctools_get_relationships(); + + // Go through each relationship + foreach ($relationships as $rid => $relationship) { + // For each relationship, see if there is a context that satisfies it. + if (empty($relationship['no ui']) && ctools_context_filter($contexts, $relationship['required context'])) { + $relevant[$rid] = $relationship['title']; + } + } + + return $relevant; +} + +/** + * Fetch all active relationships + * + * @param $relationships + * An keyed array of relationship data including: + * - name: name of relationship + * - context: context id relationship belongs to. This will be used to + * identify which context in the $contexts array to use to create the + * relationship context. + * + * @param $contexts + * A keyed array of contexts used to figure out which relationships + * are relevant. New contexts will be added to this. + * + * @param $placeholders + * If TRUE, placeholders are acceptable. + */ +function ctools_context_get_context_from_relationships($relationships, &$contexts, $placeholders = FALSE) { + $return = array(); + + foreach ($relationships as $rdata) { + if (!isset($rdata['context'])) { + continue; + } + + if (is_array($rdata['context'])) { + $rcontexts = array(); + foreach ($rdata['context'] as $cid) { + if (empty($contexts[$cid])) { + continue 2; + } + $rcontexts[] = $contexts[$cid]; + } + } + else { + if (empty($contexts[$rdata['context']])) { + continue; + } + $rcontexts = $contexts[$rdata['context']]; + } + + $cid = ctools_context_id($rdata, 'relationship'); + if ($context = ctools_context_get_context_from_relationship($rdata, $rcontexts)) { + $contexts[$cid] = $context; + } + } +} + +// --------------------------------------------------------------------------- +// Functions related to loading contexts from simple context definitions. + +/** + * Fetch metadata on a specific context plugin. + * + * @param $context + * Name of a context. + * + * @return + * An array with information about the requested panel context. + */ +function ctools_get_context($context) { + static $gate = array(); + ctools_include('plugins'); + $plugin = ctools_get_plugins('ctools', 'contexts', $context); + if (empty($gate['context']) && !empty($plugin['superceded by'])) { + // This gate prevents infinite loops. + $gate[$context] = TRUE; + $new_plugin = ctools_get_plugins('ctools', 'contexts', $plugin['superceded by']); + $gate[$context] = FALSE; + + // If a new plugin was returned, return it. Otherwise fall through and + // return the original we fetched. + if ($new_plugin) { + return $new_plugin; + } + } + + return $plugin; +} + +/** + * Fetch metadata for all context plugins. + * + * @return + * An array of arrays with information about all available panel contexts. + */ +function ctools_get_contexts() { + ctools_include('plugins'); + return ctools_get_plugins('ctools', 'contexts'); +} + +/** + * + * @param $context + * The configuration of a context. It must contain the following data: + * - name: The name of the context plugin being used. + * - context_settings: The configuration based upon the plugin forms. + * - identifier: The human readable identifier for this context, usually + * defined by the UI. + * - keyword: The keyword used for this context for substitutions. + * @param $type + * This is either 'context' which indicates the context will be loaded + * from data in the settings, or 'required_context' which means the + * context must be acquired from an external source. This is the method + * used to pass pure contexts from one system to another. + * + * @return + * A context object if one can be loaded. + */ +function ctools_context_get_context_from_context($context, $type = 'context', $argument = NULL) { + ctools_include('plugins'); + $plugin = ctools_get_context($context['name']); + if ($function = ctools_plugin_get_function($plugin, 'context')) { + // Backward compatibility: Merge old style settings into new style: + if (!empty($context['context_settings'])) { + $context += $context['context_settings']; + unset($context['context_settings']); + } + + if (isset($argument) && isset($plugin['placeholder name'])) { + $context[$plugin['placeholder name']] = $argument; + } + + $return = $function($type == 'requiredcontext', $context, TRUE, $plugin); + if ($return) { + $return->identifier = $context['identifier']; + $return->page_title = isset($context['title']) ? $context['title'] : ''; + $return->keyword = $context['keyword']; + + if (!empty($context->empty)) { + $context->placeholder = array( + 'type' => 'context', + 'conf' => $context, + ); + } + + return $return; + } + } +} + +/** + * Retrieve a list of base contexts based upon a simple 'contexts' definition. + * + * For required contexts this will always retrieve placeholders. + * + * @param $contexts + * The list of contexts defined in the UI. + * @param $type + * Either 'context' or 'requiredcontext', which indicates whether the contexts + * are loaded from internal data or copied from an external source. + * @param $placeholders + * If true, placeholders are acceptable. + */ +function ctools_context_get_context_from_contexts($contexts, $type = 'context', $placeholders = FALSE) { + $return = array(); + foreach ($contexts as $context) { + $ctext = ctools_context_get_context_from_context($context, $type); + if ($ctext) { + if ($placeholders) { + $ctext->placeholder = TRUE; + } + $return[ctools_context_id($context, $type)] = $ctext; + } + } + return $return; +} + +/** + * Match up external contexts to our required contexts. + * + * This function is used to create a list of contexts with proper + * IDs based upon a list of required contexts. + * + * These contexts passed in should match the numeric positions of the + * required contexts. The caller must ensure this has already happened + * correctly as this function will not detect errors here. + * + * @param $required + * A list of required contexts as defined by the UI. + * @param $contexts + * A list of matching contexts as passed in from the calling system. + */ +function ctools_context_match_required_contexts($required, $contexts) { + $return = array(); + if (!is_array($required)) { + return $return; + } + + foreach ($required as $r) { + $context = clone(array_shift($contexts)); + $context->identifier = $r['identifier']; + $context->page_title = isset($r['title']) ? $r['title'] : ''; + $context->keyword = $r['keyword']; + $return[ctools_context_id($r, 'requiredcontext')] = $context; + } + + return $return; +} + +/** + * Load a full array of contexts for an object. + * + * Not all of the types need to be supported by this object. + * + * This function is not used to load contexts from external data, but may + * be used to load internal contexts and relationships. Otherwise it can also + * be used to generate a full set of placeholders for UI purposes. + * + * @param $object + * An object that contains some or all of the following variables: + * + * - requiredcontexts: A list of UI configured contexts that are required + * from an external source. Since these require external data, they will + * only be added if $placeholders is set to TRUE, and empty contexts will + * be created. + * - arguments: A list of UI configured arguments that will create contexts. + * Since these require external data, they will only be added if $placeholders + * is set to TRUE. + * - contexts: A list of UI configured contexts that have no external source, + * and are essentially hardcoded. For example, these might configure a + * particular node or a particular taxonomy term. + * - relationships: A list of UI configured contexts to be derived from other + * contexts that already exist from other sources. For example, these might + * be used to get a user object from a node via the node author relationship. + * @param $placeholders + * If TRUE, this will generate placeholder objects for types this function + * cannot load. + * @param $contexts + * An array of pre-existing contexts that will be part of the return value. + */ +function ctools_context_load_contexts($object, $placeholders = TRUE, $contexts = array()) { + if (!empty($object->base_contexts)) { + $contexts += $object->base_contexts; + } + + if ($placeholders) { + // This will load empty contexts as placeholders for arguments that come + // from external sources. If this isn't set, it's assumed these context + // will already have been matched up and loaded. + if (!empty($object->requiredcontexts) && is_array($object->requiredcontexts)) { + $contexts += ctools_context_get_context_from_contexts($object->requiredcontexts, 'requiredcontext', $placeholders); + } + + if (!empty($object->arguments) && is_array($object->arguments)) { + $contexts += ctools_context_get_placeholders_from_argument($object->arguments); + } + } + + if (!empty($object->contexts) && is_array($object->contexts)) { + $contexts += ctools_context_get_context_from_contexts($object->contexts, 'context', $placeholders); + } + + // add contexts from relationships + if (!empty($object->relationships) && is_array($object->relationships)) { + ctools_context_get_context_from_relationships($object->relationships, $contexts, $placeholders); + } + + return $contexts; +} + +/** + * Return the first context with a form id from a list of contexts. + * + * This function is used to figure out which contexts represents 'the form' + * from a list of contexts. Only one contexts can actually be 'the form' for + * a given page, since the @code{