array( 'label' => t('Rules Link'), 'entity class' => 'RulesLink', 'controller class' => 'EntityAPIControllerExportable', 'base table' => 'rules_link', 'fieldable' => TRUE, 'entity keys' => array( 'id' => 'id', 'name' => 'name', 'label' => 'label', ), 'exportable' => TRUE, 'module' => 'rules_link', 'access callback' => 'rules_link_access', // Enable the entity API's admin UI. 'admin ui' => array( 'path' => 'admin/config/workflow/rules_links', 'file' => 'rules_link.admin.inc', 'controller class' => 'RulesLinkUIController', ), ), ); } /** * Access callback for the entity API. */ function rules_link_access($op, $type = NULL, $account = NULL) { return user_access('administer rules links', $account); } /** * Menu argument loader; Load a rules link by string. * * @param $name * The machine-readable name of a rules link to load. * @return * A rules link array or FALSE if $name does not exist. */ function rules_link_load($name) { return rules_link_get_links($name); } /** * Implements hook_permission(). */ function rules_link_permission() { $permissions = array( 'administer rules links' => array( 'title' => t('Administer rules links'), 'description' => t('Create and delete rules links and set their permissions.'), ), ); foreach (rules_link_get_links() as $link) { $name = check_plain($link->name); $permissions += array( "access rules link $name" => array( 'title' => t('%link_label: Execute rules link', array('%link_label' => $link->label)), ), ); } return $permissions; } /** * Gets an array of all rules links, keyed by the type name. * * @param $link_names * If set, the function will return the link with the given name. * @return RuleLinks[] * Depending on isset $link_names, a name single or an array of rules links. */ function rules_link_get_links($link_names = NULL) { $links = entity_load_multiple_by_name('rules_link', isset($link_names) ? array($link_names) : FALSE); return isset($link_names) ? reset($links) : $links; } /** * Returns the name condition set to a rules link. */ function rules_link_get_condition_set_name($rules_link) { return 'rules_link_condition_' . $rules_link->name; } /** * Returns the name condition set to a rules link. */ function rules_link_get_rules_set_name($rules_link) { return 'rules_link_set_' . $rules_link->name; } /** * Loads and returns the condition to a link. If it * doesn't exist, a new one will be created. * * @param $rules_link * The rules link to which the condition should be loaded. */ function rules_link_load_condition_set($rules_link) { $condition_set = rules_config_load(rules_link_get_condition_set_name($rules_link)); if ($condition_set != FALSE) { return $condition_set; } else { $conditions = rules_and(array( $rules_link->entity_type => array('type' => $rules_link->entity_type, 'label' => $rules_link->entity_type), )); $conditions->label = 'Rules link: ' . $rules_link->label . ' condition'; $conditions->save(rules_link_get_condition_set_name($rules_link), 'rules_link'); return $conditions; } } /** * Loads and returns the rules set to a link. If it * doesn't exist, a new one will be created. * * @param $rules_link * The rules link to which the condition or rules set should be loaded. */ function rules_link_load_rules_set($rules_link) { $rule_set = rules_config_load(rules_link_get_rules_set_name($rules_link)); if ($rule_set != FALSE) { return $rule_set; } else { $rule_set = rules_rule_set(array( $rules_link->entity_type => array('type' => $rules_link->entity_type, 'label' => $rules_link->entity_type), )); $rule_set->label = 'Rules link: ' . $rules_link->label . ' rules set'; $rule_set->save(rules_link_get_rules_set_name($rules_link), 'rules_link'); return $rule_set; } } /** * Renders a link using the name of the rules_link and the entity id. * * @param $rules_link_name * The name the link which should be rendered. * @param $entity_id * The entity id of entity on which the rule should be triggered. * @param $destination * The destination to which the Rules Module should redirect the user after * triggering the link. * @param $parameters * Additional parameters for the Rules components of the link. * * @return * A renderable array. */ function rules_link_render($rules_link_name, $entity_id, $destination = NULL, $parameters = array()) { $rules_link = rules_link_load($rules_link_name); return rules_link_render_link($rules_link, $entity_id, $destination, $parameters); } /** * Renders a link. * * @param $rules_link * The link which should be rendered. * @param $entity_id * The entity id of entity on which the rule should be triggered. * @param $destination * The destination to which the Rules Module should redirect the user after * triggering the link. * @param $parameters * Additional parameters for the Rules components of the link. * * @return * A renderable array. */ function rules_link_render_link($rules_link, $entity_id, $destination = NULL, $parameters = array()) { if (rules_link_check_visibility($rules_link, array_merge(array($entity_id), $parameters))) { $path = $rules_link->path . '/' . $entity_id; if (count($parameters) > 0) { $path .= '/' . implode('/', $parameters); } $path .= $rules_link->settings['link_type'] == 'confirm' ? '' : '/' . rules_link_get_token($entity_id); $link = array( '#title' => $rules_link->getSettingTranslation('text'), '#href' => $path, '#attr' => array('class' => array('rules-link'), 'rel' => 'nofollow'), '#rules_link' => $rules_link, '#theme' => 'rules_link', ); if ($rules_link->settings['link_type'] == 'javascript') { $link['#attr']['class'][] = 'rules-link-js'; drupal_add_js(drupal_get_path('module', 'rules_link') . '/rules_link.js', 'file'); drupal_add_css(drupal_get_path('module', 'rules_link') . '/rules_link.css', 'file'); } else { $link['#options'] = array('query' => $destination); } return $link; } return array(); } /** * Trims a whitespaces from a parameter. */ function rules_link_trim_parameters(&$value) { $value = trim($value); } /** * Custom Entity class. */ class RulesLink extends Entity { public $settings = array(); public function __construct($values = array()) { parent::__construct($values, 'rules_link'); } /** * Gets the i18n translation of a setting. * * @param $name * The setting name. * @param $langcode * The optional language code. Defaults to the current display language. * * @see Entity::getTranslation() */ public function getSettingTranslation($name, $langcode = NULL) { $value = isset($this->settings[$name]) ? $this->settings[$name] : NULL; $i18n_name = 'rules_link:rules_link:' . $this->identifier() . ':' . $name; return entity_i18n_string($i18n_name, $value, $langcode); } } /** * Implements hook_rules_link_insert(). */ function rules_link_rules_link_insert($link) { // Do not directly issue menu rebuilds here to avoid potentially multiple // rebuilds. Instead, let menu_get_item() issue the rebuild on the next page. variable_set('menu_rebuild_needed', TRUE); } /** * Implements hook_rules_link_update(). */ function rules_link_rules_link_update($link) { // Make sure to only issue menu rebuilds if necessary. // @see rules_link_rules_link_insert() if (!empty($link->original) && ($link->path != $link->original->path || $link->settings['link_type'] != $link->original->settings['link_type'])) { variable_set('menu_rebuild_needed', TRUE); } } /** * Implements hook_rules_link_delete(). */ function rules_link_rules_link_delete($link) { // @see rules_link_rules_link_insert() variable_set('menu_rebuild_needed', TRUE); // Delete associate rule configs. rules_link_load_condition_set($link)->delete(); rules_link_load_rules_set($link)->delete(); } /** * Generates a token used to protect links from spoofing. */ function rules_link_get_token($content_id) { // Anonymous users get a less secure token, since it must be the same for all // anonymous users on the entire site to work with page caching. return ($GLOBALS['user']->uid) ? drupal_get_token($content_id) : drupal_hmac_base64($content_id, drupal_get_private_key() . drupal_get_hash_salt()); } /** * Checks if the given token is correct. */ function rules_link_check_token($token, $content_id) { return rules_link_get_token($content_id) === $token; } function rules_link_get_paramters($rules_link) { $args = arg(); // Remove the first arguments, that represent the url of the link. $path_args = explode('/', $rules_link->path); $args = array_slice($args, count($path_args)); return $args; } function rules_link_invoke_component($name, $args) { if ($component = rules_get_cache('comp_' . $name)) { return $component->executeByArgs($args); } } function rules_link_check_visibility($rules_link, $args) { $rule_set = rules_link_load_rules_set($rules_link); $paramInfo = $rule_set->parameterInfo(); return count($args) >= count($paramInfo) && user_access("access rules link " . $rules_link->name) && rules_link_invoke_component(rules_link_get_condition_set_name($rules_link), $args); } /** * Triggers a rule set from a rules link. */ function rules_link_trigger($rules_link, $entity_id) { rules_link_invoke_component(rules_link_get_rules_set_name($rules_link), rules_link_get_paramters($rules_link)); } /** * Menu callback for javascript links. */ function rules_link_trigger_js($rules_link, $entity_id) { rules_link_trigger($rules_link, $entity_id); $json = array( 'message' => drupal_get_messages(), ); drupal_json_output($json); } /** * Menu callback for token links. */ function rules_link_trigger_token($rules_link, $entity_id) { rules_link_trigger($rules_link, $entity_id); drupal_goto(); } /** * Form generator for the menu callback for the confirm form links. */ function rules_link_trigger_form($form, &$form_state, $rules_link, $entity_id) { $form['link'] = array( '#type' => 'hidden', '#value' => $rules_link->name, ); $form['entity_id'] = array( '#type' => 'hidden', '#value' => $entity_id, ); return confirm_form($form, filter_xss_admin($rules_link->getSettingTranslation('confirm_question')), '', filter_xss_admin($rules_link->getSettingTranslation('confirm_description'))); } /** * Submit function for the confirm form links. */ function rules_link_trigger_form_submit($form, &$form_state) { $rules_link = rules_link_load($form_state['values']['link']); rules_link_trigger($rules_link, $form_state['values']['entity_id']); } /** * Access callback function for confirm type links. */ function rules_link_access_link_confirm($rules_link, $entity_id) { if (rules_link_check_visibility($rules_link, rules_link_get_paramters($rules_link))) { return TRUE; } return FALSE; } /** * Access callback function for the token and javascript links. */ function rules_link_access_link($rules_link, $entity_id) { // The token is always the last element of the argument array. $params = rules_link_get_paramters($rules_link); $token = array_pop($params); return rules_link_check_token($token, $entity_id) && rules_link_access_link_confirm($rules_link, $entity_id); } /** * Implements hook_menu(). */ function rules_link_menu() { $item = array(); foreach (rules_link_get_links() as $name => $link) { $first_arg = (count(explode('/', $link->path))); switch ($link->settings['link_type']) { case 'javascript': $item[$link->path . '/%/%'] = array( 'page callback' => 'rules_link_trigger_js', 'page arguments' => array($link, $first_arg), 'access arguments' => array($link, $first_arg, $first_arg + 1), 'access callback' => 'rules_link_access_link', 'type' => MENU_CALLBACK, ); break; case 'token': $item[$link->path . '/%/%'] = array( 'page callback' => 'rules_link_trigger_token', 'page arguments' => array($link, $first_arg), 'access arguments' => array($link, $first_arg, $first_arg + 1), 'access callback' => 'rules_link_access_link', 'type' => MENU_CALLBACK, ); break; case 'confirm': $item[$link->path . '/%'] = array( 'page callback' => 'drupal_get_form', 'page arguments' => array('rules_link_trigger_form', $link, $first_arg), 'access arguments' => array($link, $first_arg), 'access callback' => 'rules_link_access_link_confirm', 'type' => MENU_CALLBACK, ); break; } } return $item; } /** * Implements hook_theme(). */ function rules_link_theme() { return array( 'rules_link' => array( 'template' => 'rules-link', 'variables' => array('title' => NULL, 'href' => NULL, 'attr' => NULL, 'options' => NULL, 'rules_link' => NULL), 'pattern' => 'rules-link__', ), ); } /** * Implements hook_theme_registry_alter(). */ function rules_link_theme_registry_alter(&$theme_registry) { // Override the base hook so that we do not overlay with the default entity // theme hook defined in entity_theme(). // Directly changing this setting in rules_link_theme() did not work. $theme_registry['rules_link']['base hook'] = 'rules_link'; } /** * Implements hook_views_api(). */ function rules_link_views_api() { return array( 'api' => '3.0', ); } function template_preprocess_rules_link(&$variables) { if (!isset($variables['options'])) { $variables['options'] = array(); } $variables['options'] += array('html' => FALSE); $variables['title'] = ($variables['options']['html'] ? $variables['title'] : check_plain($variables['title'])); if (isset($options['attr']['title']) && strpos($options['attr']['title'], '<') !== FALSE) { $variables['attr']['title'] = strip_tags($options['attr']['title']); } else if (!isset($options['attr']['title'])) { $variables['attr']['title'] = $variables['title']; } $variables['href'] = check_plain(url($variables['href'], $variables['options'])); $variables['attr'] = drupal_attributes($variables['attr']); } /** * Implement hook_entity_view(). */ function rules_link_entity_view($entity, $type, $view_mode, $langcode) { $links = array(); $rules_links = rules_link_get_links(); foreach ($rules_links as $name => $rules_link) { if ($rules_link->entity_type == $type && $rules_link->settings['entity_link']) { list($id, $rev, $bundle) = entity_extract_ids($type, $entity); // If the link is restricted to some bundles, verify the bundle. if ($id && (empty($rules_link->settings['bundles']) || in_array($bundle, $rules_link->settings['bundles']))) { $renderd_link = rules_link_render_link($rules_link, $id, drupal_get_destination()); if (!empty($renderd_link)) { $links[$name] = drupal_render($renderd_link); } } } } foreach ($links as $name => $link) { $entity->content['rules_links_' . $name] = array( '#markup' => $link, ); } } /** * Implements hook_features_pipe_component_alter() for fields. */ function rules_link_features_pipe_rules_link_alter(&$pipe, $data, $export) { foreach ($data as $id) { $rules_link = entity_load_single('rules_link', $id); $pipe['rules_config'][] = rules_link_get_condition_set_name($rules_link); $pipe['rules_config'][] = rules_link_get_rules_set_name($rules_link); } } /** * Alter the breadcrumb trail of the rules components. */ function rules_link_menu_breadcrumb_alter(&$active_trail, $item) { if (substr($item['href'], 22, 34) == 'rules/components/manage/rules_link') { // Parse the name out of the link. if (substr($item['href'], 57, 3) == 'set') { $start = 61; } elseif (substr($item['href'], 57, 3) == 'con') { $start = 67; } $link_name = substr($item['href'], $start, strlen($item['href'])); $pos = strpos($link_name, '/') ; if ($pos !== FALSE) { $link_name = substr($link_name, 0, $pos); } $rules_link = rules_link_load($link_name); if ($rules_link) { // Replace the link to Rules with a link to Rules Link. $active_trail[4]['title'] = 'Rules Links'; $active_trail[4]['href'] = 'admin/config/workflow/rules_links'; $active_trail[4]['options']['attributes']['title'] = 'Manage links that triggers rules.'; $active_trail[4]['localized_options'] = array(); // Replace component link with link to the current rules link. $active_trail[5]['title'] = $rules_link->label; $active_trail[5]['href'] = "admin/config/workflow/rules_links/manage/$link_name/components"; $active_trail[5]['options']['attributes']['title'] = 'Edit the current link.'; $active_trail[5]['localized_options'] = array(); } } } /** * Implements hook_field_extra_fields(). */ function rules_link_field_extra_fields() { $return = array(); $rules_links = rules_link_get_links(); foreach ($rules_links as $rules_link) { if ($rules_link->settings['entity_link']) { $entity_info = entity_get_info($rules_link->entity_type); $bundles = empty($rules_link->settings['bundles']) ? array_keys($entity_info['bundles']) : $rules_link->settings['bundles']; foreach ($bundles as $bundle) { $return[$rules_link->entity_type][$bundle]['display']['rules_links_' . $rules_link->name]['label'] = $rules_link->label; $return[$rules_link->entity_type][$bundle]['display']['rules_links_' . $rules_link->name]['description'] = ''; $return[$rules_link->entity_type][$bundle]['display']['rules_links_' . $rules_link->name]['weight'] = 0; } } } return $return; }