array(), ); fiftyone_degrees_set_rules($rules); $form_state['redirect'] = array(fiftyone_degrees_get_plugin_url() . "/$rule_name"); } /** * The callback function from the 'Shift Left' button. * * When the Shift Left button is pressed this function shifts the currently * selected rule to the left (increasing its priority). If the rule is already * in the first position it is moved to the last position. */ function fiftyone_degrees_shift_left_submit($form, &$form_state) { $rules = fiftyone_degrees_get_rules(); $selected_rule_name = $form['name']['rule_name']['#value']; if (isset($rules[$selected_rule_name])) { $rule_names = array_keys($rules); $selected_rule_key = array_search($selected_rule_name, $rule_names); $swap_rule_key = $selected_rule_key - 1; if ($swap_rule_key < 0) { $swap_rule_key = count($rule_names) - 1; } $rule_names[$selected_rule_key] = $rule_names[$swap_rule_key]; $rule_names[$swap_rule_key] = $selected_rule_name; $new_rules = array(); foreach ($rule_names as $rule_name) { $new_rules[$rule_name] = $rules[$rule_name]; } fiftyone_degrees_set_rules($new_rules); } return $form; } /** * The callback function from the 'Shift Right' button. * * When the Shift Right button is pressed this function shifts the currently * selected rule to the right (decreasing its priority). If the rule is already * in the last position it is moved to the first position. */ function fiftyone_degrees_shift_right_submit($form, &$form_state) { $rules = fiftyone_degrees_get_rules(); $selected_rule_name = $form['name']['rule_name']['#value']; if (isset($rules[$selected_rule_name])) { $rule_names = array_keys($rules); $selected_rule_key = array_search($selected_rule_name, $rule_names); $swap_rule_key = $selected_rule_key + 1; if ($swap_rule_key >= count($rule_names)) { $swap_rule_key = 0; } $rule_names[$selected_rule_key] = $rule_names[$swap_rule_key]; $rule_names[$swap_rule_key] = $selected_rule_name; $new_rules = array(); foreach ($rule_names as $rule_name) { $new_rules[$rule_name] = $rules[$rule_name]; } fiftyone_degrees_set_rules($new_rules); } return $form; } /** * The callback function from the 'Save Rule' button. * * When the Save Rule button is pressed this funciton takes the #values from * the form elements and stores in the databse. */ function fiftyone_degrees_rule_submit($form, &$form_state) { $rule = array(); // Set conditions from basic properties. $rule['conditions'] = array(); foreach ($form['basic_properties'] as $name => $value) { /* Conditions set from basic properties are prefixed with !. This shows only one of these conditions to match for the rule to apply. */ if (isset($value['#type']) && $value['#type'] === 'checkbox' && $value['#value'] === 'True') { $rule['conditions']['!' . $name] = 'True'; } } foreach ($form['advanced_properties']['list'] as $name => $value) { if (is_array($value) && isset($value[$name . '-enabled']) && $value[$name . '-enabled']['#value'] === 'True') { $rule['conditions'][$name] = $value[$name . '-value']['#value']; } } $rule['theme'] = $form['action']['theme']['#value']; if ($form['action']['theme']['#value'] === 'url_redirect' && $form['action']['url']['#value'] !== 'Enter a url here...') { $rule['url'] = $form['action']['url']['#value']; } // Get current rule set to add changes. $rules = fiftyone_degrees_get_rules(); // Check if the name was changed. if ($form['name']['rule_name']['#value'] == $form['name']['rule_new_name']['#value']) { $rules[$form['name']['rule_name']['#value']] = $rule; } else { /* Names are not the same. The array needs to be rebuilt so order can be preserved.*/ $new_rules = array(); foreach ($rules as $stored_rule_name => $stored_rule) { if ($stored_rule_name == $form['name']['rule_name']['#value']) { $new_rules[$form['name']['rule_new_name']['#value']] = $rule; } else { $new_rules[$stored_rule_name] = $stored_rule; } } $rules = $new_rules; } fiftyone_degrees_set_rules($rules); // Redirect to rule. $rule_url = fiftyone_degrees_get_plugin_url() . '\\' . $form['name']['rule_new_name']['#value']; $form_state['redirect'] = $rule_url; return $form; } /** * The callback function from the 'Delete Rule' button. * * When pressed this function deletes the current rule, giving a Drupal warning * if no rule was selected to delete. */ function fiftyone_degrees_delete_rule_submit($form, &$form_state) { $name = $form['name']['rule_name']['#value']; $rules = fiftyone_degrees_get_rules(); if (isset($rules[$name])) { unset($rules[$name]); fiftyone_degrees_set_rules($rules); $message = check_plain(t('Rule ') . $name . t(' has been deleted.')); drupal_set_message($message); $form_state['redirect'] = array(fiftyone_degrees_get_plugin_url()); } else { $message = t('Rule ') . $rule_name . t(' does not exist.'); drupal_set_message($message, 'warning'); } } /** * The callback function from the 'Save Settings' button. * * When the Save Settings button is pressed this function saves settings from * $form to the database. */ function fiftyone_degrees_settings_submit($form, &$form_state) { $share_usage = $form['settings']['share_usage']['#value']; $settings = array( 'share_usage' => $share_usage, ); fiftyone_degrees_set_settings($settings); } /** * The callback function from the 'Data Update' button. * * When te Data Update button is pressed this function first stores the licence * key and then redirects back to the module page with '~dataupdate' appended to * the url. */ function fiftyone_degrees_data_update_submit($form, &$form_state) { $licence_key = $form['data_update']['licence_key']['#value']; if (count($licence_key > 0)) { fiftyone_degrees_set_licence_key($licence_key); $library = libraries_detect('51Degrees'); file_put_contents($library['library path'] . '/licence.lic', $licence_key); $form_state['redirect'] = array(fiftyone_degrees_get_plugin_url() . '/~dataupdate'); } } /** * Implements hook_libraries_info(). */ function fiftyone_degrees_libraries_info() { $libraries = array(); $libraries['51Degrees'] = array( 'name' => '51Degrees', 'vendor url' => 'http://51Degrees.mobi/', 'download url' => 'http://51Degrees.mobi/', 'version' => '1.0', 'versions' => array(), 'dependencies' => array(), ); return $libraries; } /** * Implements hook_init(). */ function fiftyone_degrees_init() { /* If the user has access to see administration pages then check if 51Degrees library is installed. If not throw an error. */ if (user_access('access administration pages')) { $library = libraries_detect('51Degrees'); if (!$library['installed']) { drupal_set_message( t('You need to download the 51Degrees library, extract the archive and place the library in the sites/all/libraries directory on your server.', array('@fiftyone_degrees' => $library['download url']) ), 'error', FALSE); } } } /** * Implements hook_menu(). */ function fiftyone_degrees_menu() { $items = array(); // Add the configuration page only if the 51Degrees library is installed. $library = libraries_detect('51Degrees'); if ($library['installed']) { $items['admin/config/system/fiftyone_degrees'] = array( 'title' => ucfirst('51Degrees.mobi'), 'description' => 'Control how your Drupal responds and redirects to mobile devices with 51Degrees.mobi', 'page callback' => 'drupal_get_form', 'page arguments' => array('fiftyone_degrees_get_tab'), 'access arguments' => array('administer site configuration'), 'type' => MENU_NORMAL_ITEM, ); } return $items; } /** * Builds the configuration page, returning a $form array. * * @param array $rule_name * (optional)The name of the rule to be displayed. If unsuppliced or null the * first rule will be displayed. If $rule_name is '~dataupdate' the module * will attempt a data update. */ function fiftyone_degrees_get_tab($form, &$form_state, $rule_name = NULL) { if (($library = libraries_detect('51Degrees')) and $library['installed']) { // First check if a data update is required. if ($rule_name === '~dataupdate') { $form['update'] = fiftyone_degrees_update(); } else { $form['device_heading'] = array( '#markup' => '

' . t('Device Rules') . '

', ); $form['rules_description'] = array( '#markup' => '

' . t('Rules can be created to redirect different types of devices to different themes or pages. Rules are evaluated from left to right, so place your most specific rules first.') . '

', ); // Get arguments. $form['create_rule'] = array( '#type' => 'button', '#submit' => array('fiftyone_degrees_create_rule_submit'), '#value' => t('Create Rule'), '#executes_submit_callback' => TRUE, ); // Load meta data from library. $meta_data_path = fiftyone_degrees_get_meta_data_path(); require_once $meta_data_path; $rules = fiftyone_degrees_get_rules(); if (count($rules) == 0) { $form['warning'] = array( '#markup' => '

' . t('No rules have been created.') . '

', ); } else { // Make sure $rule_name has a valid value. if ($rule_name == NULL) { $names = array_keys($rules); $rule_name = $names[0]; } elseif (!isset($rules[$rule_name])) { $message = check_plain(t('Rule ') . $rule_name . t(' does not exist.')); drupal_set_message($message, 'warning'); $names = array_keys($rules); $rule_name = $names[0]; } $rule = $rules[$rule_name]; $form['name'] = fiftyone_degrees_tabs($rules, $rule_name); $form['basic_properties'] = fiftyone_degrees_basic_properties($rule, $_51d_meta_data); $form['advanced_properties'] = fiftyone_degrees_advanced_properties($rule, $_51d_meta_data); $form['action'] = fiftyone_degrees_action($rule); $form['submit'] = array( '#type' => 'button', '#value' => t('Save Rule'), '#submit' => array('fiftyone_degrees_rule_submit'), '#executes_submit_callback' => TRUE, ); $form['delete_submit'] = array( '#type' => 'button', '#value' => t('Delete Rule'), '#submit' => array('fiftyone_degrees_delete_rule_submit'), '#executes_submit_callback' => TRUE, '#attributes' => array( 'onclick' => array('return confirmDelete()'), ), ); } drupal_add_js( drupal_get_path('module', 'fiftyone_degrees') . '/fiftyone_degrees_script.js', array( 'type' => 'file', 'defer' => TRUE, ) ); // Add settings. $form['settings'] = fiftyone_degrees_settings(); // Add data update. $form['data_update'] = fiftyone_degrees_data_update($_51d_meta_data); } } $form['#attached']['css'] = array( drupal_get_path('module', 'fiftyone_degrees') . '/fiftyone_degrees_menu.css', ); return $form; } /** * Gets a $form array for the tabs to be displayed at top of the module page. * * @param array $rules * An associative array of rules * * @param string $selected_rule_name * The name of the selected rule. The function is assumes this to be a valid * rule name. */ function fiftyone_degrees_tabs($rules, $selected_rule_name) { $plugin_url = fiftyone_degrees_get_plugin_url(); $form = array( '#type' => 'container', '#attributes' => array( 'class' => array('tabs'), ), ); // Add a button to increase priority of this rule. $form['shift_left'] = array( '#type' => 'button', '#value' => 'Shift Left', '#submit' => array('fiftyone_degrees_shift_left_submit'), '#executes_submit_callback' => TRUE, ); foreach ($rules as $rule_name => $rule) { // Check if this rule is the selected rule. if ($rule_name == $selected_rule_name) { // A text field to rename this rule. $form['rule_new_name'] = array( '#type' => 'textfield', '#default_value' => $rule_name, ); // A hidden field to identify the rule if the name is changed. $form['rule_name'] = array( '#type' => 'hidden', '#value' => $rule_name, ); } else { $form['rule_tab_' . $rule_name] = array( '#type' => 'container', ); $form['rule_tab_' . $rule_name]['markup'] = array( '#markup' => "$rule_name", ); } } // Add a button to decrease priority of this rule. $form['shift_right'] = array( '#type' => 'button', '#value' => 'Shift Right', '#submit' => array('fiftyone_degrees_shift_right_submit'), '#executes_submit_callback' => TRUE, ); return $form; } /** * Creates a $form array for the basic properties section. * * @param array $rule * The rule array of the selected rule. * * @param array $metadata * An associative array of 51Degrees metadata, populated by * 51Degrees.mobi.metadata.php. * * @return array * A $form. */ function fiftyone_degrees_basic_properties($rule, $metadata) { /* The keys of this array show what items will be listed as a basic property. values show the equivalent data to use from the metadata. */ $properties = array( 'Mobile' => 'IsMobile', 'Small Screen Phone' => 'IsSmallScreen', 'SmartPhone' => 'IsSmartPhone', 'Tablet' => 'IsTablet', 'Console' => 'IsConsole', 'EReader' => 'IsEReader', ); // An array of descriptions to use with the $properties array. $property_descriptions = array( 'IsMobile' => 'Indicates that the device\'s primary data connection is wireless and is designed to operate mostly from battery power (ie a mobile phone, smart phone or tablet etc)', 'IsSmallScreen' => 'Is a mobile device with a screen size smaller than 2 1/2 inches.', 'IsSmartPhone' => 'Is a mobile device with a screen size larger then 2 1/2 inches running a modern smart phone operating system including Android, iOS, BlackBerry or Windows Phone.', 'IsTablet' => 'The manufacturer of the device sells the device primarily as a tablet.', 'IsConsole' => 'Indicates if the device is neither a phone or a computer/tablet (ie a games console or television).', 'IsEReader' => 'Indicates if the device is an e-reader.', ); $form = array(); $form['header'] = array( '#markup' => '

Basic Properties

', ); $form['description'] = array( '#markup' => '

' . t('Only one condition matched here is needed for the rule to be used.') . '

', ); /* This array will contain basic properties that are not in the current data to include in the disabled properties section. */ $disabled_props = array(); foreach ($properties as $name => $property) { if (isset($metadata[$property])) { /* ! means it's a basic property, only one of these need to be matched later for a redirect. */ $check_box_value = isset($rule['conditions']["!$property"]) && $rule['conditions']["!$property"] == 'True'; $form[$property] = array( '#type' => 'checkbox', '#title' => $name, '#default_value' => $check_box_value, '#return_value' => 'True', '#suffix' => "

$property_descriptions[$property]

", ); } // Property not available in metadata, include in disabled properties. else { $disabled_props[$name] = $property; } } // Write disabled properties section. if (empty($disabled_props) == FALSE) { $form['disabled_properties'] = array( '#type' => 'container', '#attributes' => array('name' => array('disabled')), ); $form['disabled_properties']['link'] = array( '#type' => 'link', '#title' => 'Upgrade to enable additional features', '#href' => 'http://51degrees.mobi/Purchase/PremiumData.aspx', '#attributes' => array( 'target' => array('_blank'), ), ); foreach ($disabled_props as $name => $property) { $form['disabled_properties']["!$property"] = array( '#type' => 'checkbox', '#title' => $name, '#default_value' => FALSE, '#suffix' => "

$property_descriptions[$property]

", '#disabled' => TRUE, '#attributes' => array('enabled' => array('False')), ); } } return $form; } /** * Creates a $form array for the advanced properties section. * * @param array $rule * The rule array of the selected rule. * * @param array $metadata * An associative array of 51Degrees metadata, populated by * 51Degrees.mobi.metadata.php. * * @return array * A $form. */ function fiftyone_degrees_advanced_properties($rule, $metadata) { $form = array( '#type' => 'container', '#attributes' => array( 'class' => array('advanced_properties'), ), ); $form['heading'] = array( '#markup' => '

Advanced Properties

', ); $form['description'] = array( '#markup' => '

' . t('All properties selected here need to be matched for the rule to be used. Use the checkbox to enable the property, and the drop down menu to select what the value should be.') . '

', ); $form['toggle'] = array( '#markup' => '' . t('(show)') . '', ); $form['list'] = array( '#type' => 'container', '#attributes' => array( 'id' => array('advanced_properties_list'), ), ); // Sort keys alphabetically. ksort($metadata); foreach ($metadata as $property_name => $property) { // DatasetName and Date aren't device properties. if ($property_name == 'DatasetName' || $property_name == 'Date') { continue; } $form['list'][$property_name] = array( '#type' => 'container', '#attributes' => array( 'class' => array('row'), ), ); $check_box_id = "rule_enabled_$property_name"; // The checkbox is enabled if any value for that property is stored. $values = array_keys($property['Values']); // Sort values alphabetically. asort($values); $property_enabled = isset($rule['conditions'][$property_name]) && (array_search( $rule['conditions'][$property_name], $values) !== FALSE); $form['list'][$property_name][$property_name . '-enabled'] = array( '#type' => 'checkbox', '#default_value' => $property_enabled, '#return_value' => 'True', '#attributes' => array( //'id' => $check_box_id, 'onclick' => 'toggleRow(event)', ), ); $form['list'][$property_name]['name'] = array( '#type' => 'container', ); $form['list'][$property_name]['name']['markup'] = array( '#markup' => $property_name, ); $select_id = "rule_value_$property_name"; $display_values = array(); foreach($values as $value) { $display_values[$value] = $value; } $form['list'][$property_name][$property_name . '-value'] = array( '#type' => 'select', '#options' => $display_values, '#attributes' => array( //'id' => array($select_id), 'class' => array('value'), ), ); // Set the current property as default value if one has been set. if ($property_enabled) { $form['list'][$property_name][$property_name . '-value']['#default_value'] = $rule['conditions'][$property_name]; } } return $form; } /** * Creates a $form array for the action section. * * This section displays a theme select menu and a url to redirect to. * * @param array $rule * The rule array of the selected rule. * * @return array * A $form. */ function fiftyone_degrees_action($rule) { $form = array(); $form['header'] = array( '#markup' => '

Action

', ); $form['description'] = array( '#markup' => '

' . t('Select what this rule should do if its conditions are matched. A new theme can be used, or the device can be redirected to any web page.') . '

', ); $options = array(); foreach (fiftyone_degrees_get_themes() as $theme) { $options[$theme] = $theme; } $options['url_redirect'] = '[Redirect to url...]'; $form['theme'] = array( '#type' => 'select', '#options' => $options, '#attributes' => array( 'id' => array('theme_list'), 'onclick' => array('checkRedirectText()'), ), ); if (isset($rule['theme'])) { $form['theme']['#default_value'] = $rule['theme']; } else { $form['theme']['#default_value'] = 'url_redirect'; } if (isset($rule['url'])) { $current_url = $rule['url']; } else { $current_url = 'Enter a url here...'; } $form['url'] = array( '#type' => 'textfield', '#default_value' => $current_url, '#attributes' => array( 'id' => array('url_textfield'), ), ); return $form; } /** * Creates a $form array for the settings section. * * @return array * A $form. */ function fiftyone_degrees_settings() { $settings = fiftyone_degrees_get_settings(); $form = array( '#type' => 'container', ); $form['header'] = array( '#markup' => '

Global Settings

', ); $form['share_usage'] = array( '#type' => 'checkbox', '#title' => t('Share usage data with 51Degrees.mobi.') . 'Learn More', '#return_value' => TRUE, '#default_value' => isset($settings['share_usage']) && $settings['share_usage'] === TRUE, ); $form['submit'] = array( '#type' => 'button', '#value' => t('Save Settings'), '#submit' => array('fiftyone_degrees_settings_submit'), '#executes_submit_callback' => TRUE, ); return $form; } /** * Creates a $form array for the update section. * * @param array $metadata * An associative array of 51Degrees metadata, populated by * 51Degrees.mobi.metadata.php. This is required to show the data publish * date. * * @return array * A $form. */ function fiftyone_degrees_data_update($metadata) { // Returns an empty string if one has not been set yet. $licence_value = fiftyone_degrees_get_licence_key(); $form = array( '#type' => 'container', ); $form['heading'] = array( '#markup' => '

Upgrade Device Data (Beta)

', ); $form['info'] = array( '#markup' => '

' . t('if you have purchased a licence key for device data paste it into the text box below and select Update. This process requires your PHP environment to support file updates and outbound web connectivity.') . '

', ); $form['licence_key'] = array( '#type' => 'textfield', '#title' => t('Licence Key: '), '#default_value' => $licence_value, ); $form['submit'] = array( '#type' => 'button', '#value' => t('Update Data'), '#submit' => array('fiftyone_degrees_data_update_submit'), '#executes_submit_callback' => TRUE, ); $data_type = $metadata['DatasetName']; $published_date = $metadata['Date']; $form['data_info'] = array( '#markup' => t('Data Type: ') . $data_type . t(' Published Date: ') . $published_date, ); return $form; } /** * Gets array of active theme names. * * @return array * An array of theme names. */ function fiftyone_degrees_get_themes() { $themes = array(); foreach (list_themes() as $theme) { // Themes have only been enabled if there status is 1. if ($theme->status == 1) { $themes[] = $theme->name; } } return $themes; } /** * Gets the url to the module page. * * @return string * The url of the module page. */ function fiftyone_degrees_get_plugin_url() { global $base_url; return $base_url . '/admin/config/system/fiftyone_degrees'; } /** * Gets a $form array to display data update progress. * * Javascript invoked from this form calls a php page within the 51Degrees * library that manages the update. */ function fiftyone_degrees_update() { $form = array(); $form['heading'] = array( '#markup' => '

' . t('Device Data Update') . '

', ); $form['description'] = array( '#markup' => '

' . t('51Degrees.mobi Device Detector will now attempt to the detection libary. Keep this page open, this may take some time.') . '

', ); $form['messages'] = array( '#type' => 'container', '#attributes' => array( 'id' => 'update_message', ), ); $form['finished_message'] = array( '#type' => 'container', '#attributes' => array( 'id' => 'finished_message', ), ); $library_path = libraries_get_path('51Degrees'); drupal_add_js( "$library_path/51DUpdate.js", array( 'type' => 'file', ) ); global $base_url; $update_location = "$base_url//$library_path//51DUpdate.php"; drupal_add_js( "fiftyone_degrees_update_location = '$update_location';", 'inline' ); drupal_add_js( drupal_get_path( 'module', 'fiftyone_degrees') . '/fiftyone_degrees_update_script.js', 'file' ); return $form; } /** * Implements hook_custom_theme(). */ function fiftyone_degrees_custom_theme() { // Never retheme admin . if (arg(0) !== 'admin') { if (($library = libraries_detect('51Degrees')) and $library['installed']) { // Check if share usage is enabled. $settings = fiftyone_degrees_get_settings(); if (isset($settings['share_usage']) && $settings['share_usage']) { $share_usage_path = fiftyone_degrees_get_share_usage_path(); require_once $share_usage_path; } $detector_path = fiftyone_degrees_get_detector_path(); require_once $detector_path; return fiftyone_degrees_evaulate_rules($_51d); } } } /** * Evaluates to detection rules. * * If a redirection is needed this function will immediately redirect the * request. * * @param array $_51d * An associative array of properties generated by 51Degrees.mobi. * * @return string * The name of a valid theme to use. */ function fiftyone_degrees_evaulate_rules($_51d) { // Check if 'NO_SWITCH' has been set. if (isset($_SESSION['NO_SWITCH']) && $_SESSION['NO_SWITCH'] === TRUE) { return; } $rules = fiftyone_degrees_get_rules(); foreach ($rules as $rule_name => $rule) { $is_match = TRUE; foreach ($rule['conditions'] as $property => $value) { /* ! means that a property is important. Any match should trigger redirect.*/ if ($property[0] == '!') { $important_match = TRUE; // Strip out ! so it can be used to index the $_51d array. $device_value = $_51d[str_replace('!', '', $property)]; } // Check if the match is still valid. elseif ($is_match) { $important_match = FALSE; $device_value = $_51d[$property]; } // The loop must continue incase an important property is found later. else { continue; } if ($device_value != $value) { // No match. $is_match = FALSE; } elseif ($important_match) { /* The property was important. Skip checking the rest and move straight onto redirection. */ $is_match = TRUE; break; } } // If a theme or url is not available for use the match loop iterates again. if ($is_match) { if (isset($rule['theme'])) { // Check if a theme or redirect should be used by. if ($rule['theme'] === 'url_redirect') { if (isset($rule['url'])) { header("location: " . $rule['url']); exit; } } else { // Check if theme exists. $themes = fiftyone_degrees_get_themes(); foreach ($themes as $theme) { // Only use theme if it is still available. if ($rule['theme'] === $theme) { return $theme; } } } } } } } /** * Implements hook_theme(). */ function fiftyone_degrees_theme() { $path = drupal_get_path('module', '51Degrees'); return array( 'switcher' => array( 'variables' => array( 'metadata' => NULL, 'commonProperties' => NULL, 'currentData' => NULL, ), ), ); } /** * Gets the path to the detector in the 51Degrees library. */ function fiftyone_degrees_get_detector_path() { $library = libraries_detect('51Degrees'); return $library['library path'] . '/51Degrees.mobi.php'; } /** * Gets the path to the meta data in the 51Degrees library. */ function fiftyone_degrees_get_meta_data_path() { $library = libraries_detect('51Degrees'); return $library['library path'] . '/51Degrees.mobi.metadata.php'; } /** * Gets the path to the share usage script in the 51Degrees library. */ function fiftyone_degrees_get_share_usage_path() { $library = libraries_detect('51Degrees'); return $library['library path'] . '/51Degrees.mobi.usage.php'; }