├── fastly.info ├── fastly.install ├── fastly.rules.inc ├── fastly.rules_defaults.inc ├── README.txt ├── fastly.module ├── fastly.api.inc ├── example.vcl └── fastly.admin.inc /fastly.info: -------------------------------------------------------------------------------- 1 | name = Fastly 2 | description = Integration with the Fastly service 3 | package = Reports 4 | core = 7.x 5 | configure = admin/config/services/fastly 6 | files[] = fastly.api.inc 7 | -------------------------------------------------------------------------------- /fastly.install: -------------------------------------------------------------------------------- 1 | t('Purge the page.'), 16 | 'group' => t('Fastly'), 17 | ); 18 | 19 | $actions['fastly_rules_action_purge_by_urls'] = array( 20 | 'label' => t('Purge by URL(s).'), 21 | 'group' => t('Fastly'), 22 | 'parameter' => array( 23 | 'urls' => array( 24 | 'type' => 'text', 25 | 'label' => t('Absolute URL or internal path of page to clear'), 26 | 'description' => t('Paste one or more URLs to purge. Each in new line. Examples: http://example.com, http://example.com/node/1, user/1, <front>.'), 27 | ), 28 | ), 29 | ); 30 | 31 | return $actions; 32 | } 33 | 34 | /** 35 | * Rules action to purge the URL after the content is updated. 36 | */ 37 | function fastly_rules_action_purge() { 38 | $node = menu_get_object(); 39 | $api = fastly_get_api(); 40 | $api->purgePath('node/' . $node->nid); 41 | } 42 | 43 | /** 44 | * Rules action to purge the specified URL(s). 45 | * 46 | * @param array $urls 47 | * Array with user-defined URLs and internal paths. 48 | */ 49 | function fastly_rules_action_purge_by_urls($urls) { 50 | $api = fastly_get_api(); 51 | foreach (explode("\r\n", $urls) as $line) { 52 | $api->purgePath(trim($line)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /fastly.rules_defaults.inc: -------------------------------------------------------------------------------- 1 | Site building > Modules. 60 | 61 | 3. Rebuild access permissions if you are prompted to. 62 | 63 | 4. Profit! Fastly will appear in your Configuration > Web services menu section. 64 | 65 | If you find a problem, incorrect comment, obsolete or improper code or such, 66 | please search for an issue about it at http://drupal.org/project/fastly/issues 67 | If there isn't already an issue for it, please create a new one. 68 | 69 | SSL and Fastly 70 | -------------- 71 | Fastly can support SSL connections. 72 | See http://docs.fastly.com/guides/21844521/23340542 for a list of different 73 | options available. 74 | If you are using SSL, you should add the following lines of code to your 75 | settings.php 76 | 77 | // Enable Faslty SSL connections. 78 | if (isset($_SERVER['HTTP_FASTLY_SSL']) && $_SERVER['HTTP_FASTLY_SSL']) { 79 | $_SERVER['HTTPS'] = 'on'; 80 | } 81 | 82 | Expire Module Integration 83 | ------------------------- 84 | The Faslty module has integration with the Cache Expiration module 85 | (https://www.drupal.org/project/expire). 86 | 87 | You can enable this by visiting admin/config/system/expire. You should see 88 | Fastly in the list of modules that support external expiration. 89 | Make sure you select "External expiration", and also ensure you untick "Include 90 | base URL in expires". 91 | 92 | Custom VCL 93 | ---------- 94 | Fastly gives you the ability to upload and use your own VCL file. 95 | This needs to be enabled by contacting Fastly support and requesting it. 96 | 97 | This module contains an example.vcl file, this is intended to be used with a 98 | Drupal site running on Fastly. It contains session cookie handling logic, 99 | as well as excluding certain paths from being cached. 100 | 101 | If you need to edit the VCL file for whatever reason, please be aware of the 102 | following thing: 103 | * Fastly uses Varnish 2, there are some differences in syntax between 2 and 3. 104 | * Do not include any host information in the VCL, this is added later by Fastly. 105 | -------------------------------------------------------------------------------- /fastly.module: -------------------------------------------------------------------------------- 1 | array( 63 | 'title' => t('Administer Fastly'), 64 | 'description' => t('Allows users to administer Fastly.'), 65 | 'restrict access' => TRUE, 66 | ), 67 | ); 68 | 69 | return $perms; 70 | } 71 | 72 | /** 73 | * Implements hook_menu(). 74 | */ 75 | function fastly_menu() { 76 | $items = array(); 77 | 78 | $items['admin/config/services/fastly'] = array( 79 | 'title' => 'Fastly configuration', 80 | 'description' => 'Fastly configuration', 81 | 'page callback' => 'fastly_select_page', 82 | 'access arguments' => array('administer fastly'), 83 | 'type' => MENU_NORMAL_ITEM, 84 | ); 85 | 86 | $items['admin/config/services/fastly/new'] = array( 87 | 'title' => 'Create a service', 88 | 'description' => 'Create a service', 89 | 'page callback' => 'drupal_get_form', 90 | 'page arguments' => array('fastly_new_service_form'), 91 | 'access arguments' => array('administer fastly'), 92 | 'file' => 'fastly.admin.inc', 93 | 'type' => MENU_NORMAL_ITEM, 94 | ); 95 | 96 | $items['admin/config/services/fastly/config'] = array( 97 | 'title' => 'Configuration', 98 | 'description' => 'Fastly configuration', 99 | 'page callback' => 'drupal_get_form', 100 | 'page arguments' => array('fastly_setup_form'), 101 | 'access arguments' => array('administer fastly'), 102 | 'file' => 'fastly.admin.inc', 103 | 'type' => MENU_LOCAL_TASK, 104 | ); 105 | 106 | $items['admin/config/services/fastly/register'] = array( 107 | 'title' => 'Registration', 108 | 'description' => 'Fastly registration', 109 | 'page callback' => 'drupal_get_form', 110 | 'page arguments' => array('fastly_register_form'), 111 | 'access arguments' => array('administer fastly'), 112 | 'file' => 'fastly.admin.inc', 113 | 'type' => MENU_LOCAL_TASK, 114 | ); 115 | 116 | $items['admin/config/services/fastly/purge'] = array( 117 | 'title' => 'Purge cache', 118 | 'description' => 'Fastly purge cache', 119 | 'page callback' => 'drupal_get_form', 120 | 'page arguments' => array('fastly_purge_form'), 121 | 'access arguments' => array('administer fastly'), 122 | 'file' => 'fastly.admin.inc', 123 | 'type' => MENU_LOCAL_TASK, 124 | 'weight' => -10, 125 | ); 126 | 127 | return $items; 128 | } 129 | 130 | /** 131 | * Menu callback. Redirect the user to the right page. 132 | */ 133 | function fastly_select_page() { 134 | if (variable_get('fastly_api_key', FALSE)) { 135 | $path = 'admin/config/services/fastly/config'; 136 | } 137 | else { 138 | $path = 'admin/config/services/fastly/register'; 139 | } 140 | drupal_goto($path); 141 | } 142 | 143 | /** 144 | * Returns the API object. 145 | * 146 | * The key and service id can be overriden for validation reasons. 147 | */ 148 | function fastly_get_api($api_key = '', $service_id = '') { 149 | if (empty($api_key)) { 150 | $api_key = variable_get('fastly_api_key', ''); 151 | } 152 | 153 | if (empty($service_id)) { 154 | $service_id = variable_get('fastly_service_id', ''); 155 | } 156 | 157 | return new Fastly($api_key, $service_id); 158 | } 159 | /** 160 | * Implements hook_expire_cache(). 161 | * 162 | * Provides integration with the Cache Expiration (expire) module. 163 | */ 164 | function fastly_expire_cache($urls, $wildcards, $object_type, $object) { 165 | $api = fastly_get_api(); 166 | foreach ($urls as $url) { 167 | $api->purgePath($url); 168 | } 169 | 170 | // For wildcards, we use the Surrogate-Key purging functionality. 171 | // Surrogate-Key headers are set in the response based on the 172 | // url path segments. 173 | // @See fastly_exit(). 174 | foreach ($wildcards as $path => $wildcard) { 175 | if ($wildcard) { 176 | $api->purgeKey($path); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /fastly.api.inc: -------------------------------------------------------------------------------- 1 | api_key = $api_key; 17 | $this->service_id = $service_id; 18 | $this->host = 'https://api.fastly.com/'; 19 | // $this->host = 'http://stg.fastly.com/'; 20 | } 21 | 22 | /** 23 | * Registers a new customer. 24 | * 25 | * @param array $data 26 | * Data to post to Fastly for the signup request. 27 | * 28 | * @return array 29 | * Data returned from Fastly.s 30 | */ 31 | public function signup($data) { 32 | $headers['Content-Type'] = 'application/x-www-form-urlencoded'; 33 | 34 | $result = $this->query('plugin/drupal/signup', $data, 'POST', $headers); 35 | 36 | return json_decode($result->data); 37 | } 38 | 39 | /** 40 | * Used to validate API key and service ID. 41 | * 42 | * @return bool 43 | * FALSE if any corrupt data is passed. 44 | */ 45 | public function validate() { 46 | return $this->query('current_customer')->status_message == 'OK'; 47 | } 48 | 49 | /** 50 | * Gets a list of services for the current customer. 51 | */ 52 | public function getServices() { 53 | $result = $this->query('service'); 54 | 55 | return json_decode($result->data); 56 | } 57 | 58 | /** 59 | * Creates a default service for our website once we signed up. 60 | * 61 | * @param array $data 62 | * An array of data to create the service with. 63 | * 64 | * @return object 65 | * Data returned from the Fastly request. 66 | */ 67 | public function createService($data) { 68 | $service = json_decode($this->query('service', $data, 'POST')->data); 69 | 70 | if (isset($service->id)) { 71 | $data['service'] = $service->id; 72 | 73 | $this->query('service/' . $service->id . '/version/1/domain', array('name' => $data['domain']), 'POST'); 74 | 75 | unset($data['domain']); 76 | unset($data['address']); 77 | 78 | $this->query('service/' . $service->id . '/version/1/backend', $data, 'POST'); 79 | $this->query('service/' . $service->id . '/version/1/syslog', $data, 'POST'); 80 | $this->query('service/' . $service->id . '/version/1/activate', array(), 'PUT'); 81 | } 82 | 83 | return $service; 84 | } 85 | 86 | /** 87 | * Gets the settings for a version. 88 | */ 89 | public function getSettings() { 90 | $result = $this->query('service/' . $this->service_id . '/version/' . $this->getActiveVersion() . '/settings'); 91 | 92 | return json_decode($result->data); 93 | } 94 | 95 | /** 96 | * Updates the settings for a version. 97 | * 98 | * @param array $data 99 | * An array of settings to update. 100 | */ 101 | public function updateSettings($data) { 102 | if ($this->service_id) { 103 | $active_version = $this->getActiveVersion(); 104 | 105 | $new_version = json_decode($this->query('service/' . $this->service_id . '/version/' . $active_version . '/clone', array(), 'PUT')->data); 106 | 107 | $headers['Content-Type'] = 'application/x-www-form-urlencoded'; 108 | 109 | $this->query('service/' . $this->service_id . '/version/' . $new_version->number . '/settings', $data, 'PUT', $headers); 110 | $this->query('service/' . $this->service_id . '/version/' . $new_version->number . '/activate', array(), 'PUT'); 111 | } 112 | } 113 | 114 | /** 115 | * Purge whole service. 116 | */ 117 | public function purgeAll() { 118 | $this->query('service/' . $this->service_id . '/purge_all', array(), 'POST'); 119 | } 120 | 121 | /** 122 | * Purge cache by path. 123 | */ 124 | public function purgePath($path) { 125 | global $base_url; 126 | $path = str_replace($base_url, '', $path); 127 | $this->purgeQuery($path); 128 | $this->purgeQuery(drupal_get_path_alias($path)); 129 | } 130 | 131 | /** 132 | * Performs an actual purge request for the given path. 133 | */ 134 | protected function purgeQuery($path) { 135 | drupal_http_request(url($path, array('absolute' => TRUE)), array( 136 | 'headers' => array( 137 | 'Host' => $_SERVER['HTTP_HOST'], 138 | ), 139 | 'method' => 'PURGE', 140 | )); 141 | } 142 | 143 | /** 144 | * Purge cache by key. 145 | */ 146 | public function purgeKey($key) { 147 | $this->query('service/' . $this->service_id . '/purge/' . $key, array(), 'POST'); 148 | } 149 | 150 | /** 151 | * Gets active version number for the current service. 152 | */ 153 | protected function getActiveVersion() { 154 | $service = json_decode($this->query('service/' . $this->service_id)->data); 155 | 156 | foreach ($service->versions as $version) { 157 | if ($version->active) { 158 | return $version->number; 159 | } 160 | } 161 | } 162 | 163 | /** 164 | * Performs http queries to Fastly API server. 165 | * 166 | * @param string $uri 167 | * The uri to use for the request, appended to the host. 168 | * @param array $data 169 | * (optional) Data to send with the request. 170 | * @param string $method 171 | * (optional) The method to use for the request, defaults to GET. 172 | * @param array $headers 173 | * (optional) An array of headers to send with the request. 174 | * 175 | * @return object 176 | * From drupal_http_request(). 177 | */ 178 | protected function query($uri, $data = array(), $method = 'GET', $headers = array()) { 179 | $url = $this->host . $uri; 180 | 181 | $options['headers'] = $headers; 182 | $options['method'] = $method; 183 | $options['data'] = http_build_query($data); 184 | 185 | if ($this->api_key) { 186 | $options['headers']['Fastly-Key'] = $this->api_key; 187 | } 188 | 189 | $result = drupal_http_request($url, $options); 190 | 191 | return $result; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /example.vcl: -------------------------------------------------------------------------------- 1 | sub vcl_recv { 2 | #FASTLY recv 3 | 4 | if (req.request != "HEAD" && req.request != "GET" && req.request != "FASTLYPURGE") { 5 | return(pass); 6 | } 7 | 8 | # Use anonymous, cached pages if all backends are down. 9 | if (!req.backend.healthy) { 10 | unset req.http.Cookie; 11 | } 12 | 13 | # Allow the backend to serve up stale content if it is responding slowly. 14 | set req.grace = 6h; 15 | 16 | # Do not cache these paths. 17 | if (req.url ~ "^/status\.php$" || 18 | req.url ~ "^/update\.php$" || 19 | req.url ~ "^/admin$" || 20 | req.url ~ "^/admin/.*$" || 21 | req.url ~ "^/flag/.*$" || 22 | req.url ~ "^.*/ajax/.*$" || 23 | req.url ~ "^.*/ahah/.*$") { 24 | return (pass); 25 | } 26 | 27 | # Always cache the following file types for all users. This list of extensions 28 | # appears twice, once here and again in vcl_fetch so make sure you edit both 29 | # and keep them equal. 30 | if (req.url ~ "(?i)\.(pdf|asc|dat|txt|doc|xls|ppt|tgz|csv|png|gif|jpeg|jpg|ico|swf|css|js)(\?.*)?$") { 31 | unset req.http.Cookie; 32 | } 33 | 34 | # Remove all cookies that Drupal doesn't need to know about. We explicitly 35 | # list the ones that Drupal does need, the SESS and NO_CACHE. If, after 36 | # running this code we find that either of these two cookies remains, we 37 | # will pass as the page cannot be cached. 38 | if (req.http.Cookie) { 39 | # 1. Append a semi-colon to the front of the cookie string. 40 | # 2. Remove all spaces that appear after semi-colons. 41 | # 3. Match the cookies we want to keep, adding the space we removed 42 | # previously back. (\1) is first matching group in the regsuball. 43 | # 4. Remove all other cookies, identifying them by the fact that they have 44 | # no space after the preceding semi-colon. 45 | # 5. Remove all spaces and semi-colons from the beginning and end of the 46 | # cookie string. 47 | set req.http.Cookie = ";" req.http.Cookie; 48 | set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";"); 49 | set req.http.Cookie = regsuball(req.http.Cookie, ";(SESS[a-z0-9]+|SSESS[a-z0-9]+|NO_CACHE)=", "; \1="); 50 | set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", ""); 51 | set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", ""); 52 | 53 | if (req.http.Cookie == "") { 54 | # If there are no remaining cookies, remove the cookie header. If there 55 | # aren't any cookie headers, Varnish's default behavior will be to cache 56 | # the page. 57 | unset req.http.Cookie; 58 | } 59 | else { 60 | # If there is any cookies left (a session or NO_CACHE cookie), do not 61 | # cache the page. Pass it on to Apache directly. 62 | return (pass); 63 | } 64 | } 65 | 66 | # If all that fails or drops out lets send a cached version 67 | return(lookup); 68 | } 69 | 70 | sub vcl_fetch { 71 | #FASTLY fetch 72 | 73 | if ((beresp.status == 500 || beresp.status == 503) && req.restarts < 1 && (req.request == "GET" || req.request == "HEAD")) { 74 | restart; 75 | } 76 | 77 | if(req.restarts > 0 ) { 78 | set beresp.http.Fastly-Restarts = req.restarts; 79 | } 80 | 81 | if (beresp.http.Set-Cookie) { 82 | set req.http.Fastly-Cachetype = "SETCOOKIE"; 83 | return (pass); 84 | } 85 | 86 | if (beresp.http.Cache-Control ~ "private") { 87 | set req.http.Fastly-Cachetype = "PRIVATE"; 88 | return (pass); 89 | } 90 | 91 | if (beresp.status == 500 || beresp.status == 503) { 92 | set req.http.Fastly-Cachetype = "ERROR"; 93 | set beresp.ttl = 1s; 94 | set beresp.grace = 5s; 95 | return (deliver); 96 | #error 903 "backend read error"; 97 | } 98 | 99 | if (beresp.http.Expires || beresp.http.Surrogate-Control ~ "max-age" || beresp.http.Cache-Control ~"(s-maxage|max-age)") { 100 | # keep the ttl here 101 | } else { 102 | # apply the default ttl 103 | set beresp.ttl = 3600s; 104 | } 105 | 106 | # Caching of 40x and 500 responses is disabled. 107 | # To enable it enable the setting below. 108 | if (beresp.status == 404 || beresp.status == 301 || beresp.status == 500) { 109 | #set beresp.ttl = 10m; 110 | } 111 | 112 | # Don't allow static files to set cookies. 113 | # (?i) denotes case insensitive in PCRE (perl compatible regular expressions). 114 | # This list of extensions appears twice, once here and again in vcl_recv so 115 | # make sure you edit both and keep them equal. 116 | if (req.url ~ "(?i)\.(pdf|asc|dat|txt|doc|xls|ppt|tgz|csv|png|gif|jpeg|jpg|ico|swf|css|js)(\?.*)?$") { 117 | unset beresp.http.set-cookie; 118 | } 119 | 120 | # Allow items to be stale if needed. 121 | set beresp.grace = 6h; 122 | 123 | return(deliver); 124 | } 125 | 126 | sub vcl_hit { 127 | #FASTLY hit 128 | 129 | if (!obj.cacheable) { 130 | return(pass); 131 | } 132 | return(deliver); 133 | } 134 | 135 | sub vcl_miss { 136 | #FASTLY miss 137 | return(fetch); 138 | } 139 | 140 | sub vcl_deliver { 141 | #FASTLY deliver 142 | return(deliver); 143 | } 144 | 145 | sub vcl_error { 146 | #FASTLY error 147 | 148 | # Redirect to some other URL in the case of a homepage failure. 149 | #if (req.url ~ "^/?$") { 150 | # set obj.status = 302; 151 | # set obj.http.Location = "http://backup.example.com/"; 152 | #} 153 | 154 | # Otherwise redirect to the homepage, which will likely be in the cache. 155 | set obj.http.Content-Type = "text/html; charset=utf-8"; 156 | synthetic {" 157 | 158 | 159 | Page Unavailable 160 | 166 | 167 | 168 |
169 |

Page Unavailable

170 |

The page you requested is temporarily unavailable.

171 |

We're redirecting you to the homepage in 5 seconds.

172 |
(Error "} obj.status " " obj.response {")
173 |
174 | 175 | 176 | "}; 177 | } 178 | 179 | sub vcl_pass { 180 | #FASTLY pass 181 | } 182 | -------------------------------------------------------------------------------- /fastly.admin.inc: -------------------------------------------------------------------------------- 1 | 'textfield', 18 | '#title' => t('Fastly API Key'), 19 | '#default_value' => $api_key, 20 | '#required' => TRUE, 21 | '#description' => t('You can find it on your account settings page. If you dont have an account, please go to registration page'), 22 | ); 23 | 24 | if ($api_key) { 25 | $services = array(); 26 | 27 | foreach ($api->getServices() as $service) { 28 | $services[$service->id] = $service->name; 29 | } 30 | 31 | ksort($services); 32 | 33 | $form['fastly_service_id'] = array( 34 | '#type' => 'radios', 35 | '#title' => t('Service'), 36 | '#options' => $services, 37 | '#default_value' => $service_id, 38 | '#required' => TRUE, 39 | '#description' => t('A Service represents the configuration for your website to be served through Fastly.'), 40 | ); 41 | 42 | $form['actions']['new_service'] = array( 43 | '#markup' => l(t('New service'), 'admin/config/services/fastly/new', 44 | array('attributes' => array('class' => 'button'))), 45 | '#weight' => 10, 46 | ); 47 | } 48 | 49 | $form['fastly_ttl'] = array( 50 | '#type' => 'textfield', 51 | '#title' => t('Default TTL'), 52 | '#default_value' => $service_id ? $api->getSettings()->{'general.default_ttl'} : '', 53 | '#description' => t('The default time to live for cached content in seconds.'), 54 | ); 55 | 56 | $description = t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", 57 | array( 58 | '%blog' => 'blog', 59 | '%blog-wildcard' => 'blog/*', 60 | '%front' => '', 61 | )); 62 | 63 | $form['fastly_non_cached'] = array( 64 | '#type' => 'textarea', 65 | '#title' => t('Non-cached pages'), 66 | '#default_value' => variable_get('fastly_non_cached', ''), 67 | '#description' => $description, 68 | ); 69 | 70 | $form['#submit'][] = 'fastly_setup_form_submit'; 71 | 72 | return system_settings_form($form); 73 | } 74 | 75 | /** 76 | * Register form. 77 | */ 78 | function fastly_register_form($form_state) { 79 | if (variable_get('fastly_used_registration')) { 80 | drupal_set_message(t('You are already registered. You can reset your password if you don\'t remember it.'), 'warning'); 81 | } 82 | else { 83 | global $user; 84 | 85 | $form['owner_first_name'] = array( 86 | '#type' => 'textfield', 87 | '#title' => t("First Name"), 88 | '#description' => t("The customer account owner's first name. Ex: John."), 89 | '#required' => TRUE, 90 | ); 91 | 92 | $form['owner_last_name'] = array( 93 | '#type' => 'textfield', 94 | '#title' => t("Last Name"), 95 | '#description' => t("The customer account owner's last name. Ex: Smith."), 96 | '#required' => TRUE, 97 | ); 98 | 99 | $form['owner_login'] = array( 100 | '#type' => 'textfield', 101 | '#title' => t("Email"), 102 | '#default_value' => $user->mail, 103 | '#description' => t("The owner's email to be used as login. Ex: john@somebusiness.com."), 104 | '#required' => TRUE, 105 | ); 106 | 107 | $form['owner_password'] = array( 108 | '#type' => 'password', 109 | '#title' => t("Password"), 110 | '#default_value' => '', 111 | '#required' => TRUE, 112 | ); 113 | 114 | $form['confirm_password'] = array( 115 | '#type' => 'password', 116 | '#title' => t("Confirm Password"), 117 | '#default_value' => '', 118 | '#description' => t("The customer account owner's password. Please enter 4 or more characters. Ex: P@ssW0rd!1"), 119 | '#required' => TRUE, 120 | ); 121 | 122 | $form['account_name'] = array( 123 | '#type' => 'textfield', 124 | '#title' => t('Company Name'), 125 | '#default_value' => variable_get('site_name', ''), 126 | '#description' => t('The customer account name. Ex: Some Business, LLC.'), 127 | '#required' => TRUE, 128 | ); 129 | 130 | $form['origin_ip'] = array( 131 | '#type' => 'textfield', 132 | '#title' => t('Origin Server IP'), 133 | '#default_value' => $_SERVER['SERVER_ADDR'], 134 | '#required' => TRUE, 135 | ); 136 | 137 | $form['port'] = array( 138 | '#type' => 'textfield', 139 | '#title' => t('Origin Port'), 140 | '#default_value' => '80', 141 | '#required' => TRUE, 142 | ); 143 | 144 | $form['domain_name'] = array( 145 | '#type' => 'textfield', 146 | '#title' => t('Domain Name'), 147 | '#default_value' => $_SERVER['HTTP_HOST'], 148 | '#required' => TRUE, 149 | ); 150 | 151 | $form['actions']['submit'] = array( 152 | '#type' => 'submit', 153 | '#value' => t('Sign Up'), 154 | ); 155 | 156 | $form['actions']['already_registered'] = array( 157 | '#markup' => t('Already registered?') . l(t('Click here to enter your account information and get started!'), 'admin/config/services/fastly/config'), 158 | ); 159 | 160 | $form['policy'] = array( 161 | '#markup' => '

' . t('By clicking "Sign Up" you are agreeing to the Terms of Use and Privacy Policy.') . '

', 162 | ); 163 | 164 | return $form; 165 | } 166 | } 167 | 168 | /** 169 | * Purge form. 170 | */ 171 | function fastly_purge_form($form_state) { 172 | if (variable_get('fastly_service_id', '') && variable_get('fastly_api_key', '')) { 173 | $form['purge_url'] = array( 174 | '#type' => 'fieldset', 175 | '#title' => t('Purge by URL'), 176 | '#description' => t('Paste one or more URLs to purge. Each in new line.'), 177 | ); 178 | 179 | $form['purge_url']['urls_list'] = array( 180 | '#type' => 'textarea', 181 | ); 182 | 183 | $form['purge_url']['submit'] = array( 184 | '#type' => 'button', 185 | '#value' => t('Purge'), 186 | '#id' => 'urls', 187 | '#name' => 'urls', 188 | '#executes_submit_callback' => 1, 189 | ); 190 | 191 | $form['purge_key'] = array( 192 | '#type' => 'fieldset', 193 | '#title' => t('Purge by key'), 194 | '#description' => t('Paste one or more keys to purge. Each in new line.'), 195 | ); 196 | 197 | $form['purge_key']['keys_list'] = array( 198 | '#type' => 'textarea', 199 | ); 200 | 201 | $form['purge_key']['submit'] = array( 202 | '#type' => 'button', 203 | '#value' => t('Purge'), 204 | '#id' => 'keys', 205 | '#name' => 'keys', 206 | '#executes_submit_callback' => 1, 207 | ); 208 | 209 | $form['purge_all'] = array( 210 | '#type' => 'fieldset', 211 | '#title' => t('Purge all'), 212 | '#description' => t('Purge whole service. You might not use this function too often.'), 213 | ); 214 | 215 | $form['purge_all']['submit'] = array( 216 | '#type' => 'button', 217 | '#value' => t('Purge'), 218 | '#id' => 'all', 219 | '#name' => 'all', 220 | '#executes_submit_callback' => 1, 221 | ); 222 | 223 | return $form; 224 | } 225 | else { 226 | drupal_set_message(t('You need to set up your API key and service ID to use this form.'), 'warning'); 227 | } 228 | } 229 | 230 | /** 231 | * New service form. 232 | */ 233 | function fastly_new_service_form() { 234 | $form['name'] = array( 235 | '#type' => 'textfield', 236 | '#title' => t('Service Name'), 237 | '#default_value' => variable_get('site_name', ''), 238 | '#required' => TRUE, 239 | ); 240 | 241 | $form['origin_ip'] = array( 242 | '#type' => 'textfield', 243 | '#title' => t('Origin Server IP'), 244 | '#default_value' => $_SERVER['SERVER_ADDR'], 245 | '#required' => TRUE, 246 | ); 247 | 248 | $form['port'] = array( 249 | '#type' => 'textfield', 250 | '#title' => t('Origin Port'), 251 | '#default_value' => '80', 252 | '#required' => TRUE, 253 | ); 254 | 255 | $form['domain_name'] = array( 256 | '#type' => 'textfield', 257 | '#title' => t('Domain Name'), 258 | '#default_value' => $_SERVER['HTTP_HOST'], 259 | '#required' => TRUE, 260 | ); 261 | 262 | $form['submit'] = array( 263 | '#type' => 'submit', 264 | '#value' => t('Create'), 265 | ); 266 | 267 | return $form; 268 | } 269 | 270 | /** 271 | * Implements hook_form_validate(). 272 | */ 273 | function fastly_register_form_validate($form, &$form_state) { 274 | if ($form_state['values']['owner_password'] && strlen($form_state['values']['owner_password']) < 4) { 275 | form_set_error('owner_password', t('The password must contain 4 or more characters.')); 276 | } 277 | 278 | if (!valid_email_address($form_state['values']['owner_login'])) { 279 | form_set_error('owner_login', t('The email address is invalid.')); 280 | } 281 | 282 | if ($form_state['values']['owner_password'] !== $form_state['values']['confirm_password']) { 283 | form_set_error('confirm_password', t('The passwords do not match.')); 284 | } 285 | 286 | fastly_help_message(); 287 | } 288 | 289 | /** 290 | * Implements hook_form_validate(). 291 | */ 292 | function fastly_setup_form_validate($form, &$form_state) { 293 | $api = fastly_get_api($form_state['values']['fastly_api_key']); 294 | 295 | if (!$api->validate()) { 296 | form_set_error('', t('Invalid API key.')); 297 | } 298 | 299 | fastly_help_message(); 300 | } 301 | 302 | /** 303 | * Implements hook_form_submit(). 304 | */ 305 | function fastly_setup_form_submit($form, &$form_state) { 306 | $api = fastly_get_api(); 307 | $api->updateSettings(array('general.default_ttl' => $form_state['values']['fastly_ttl'])); 308 | } 309 | 310 | /** 311 | * Implements hook_form_submit(). 312 | */ 313 | function fastly_register_form_submit($form, &$form_state) { 314 | $data = array( 315 | 'name' => $form_state['values']['account_name'], 316 | 'owner' => array( 317 | 'name' => $form_state['values']['owner_first_name'] . ' ' . $form_state['values']['owner_last_name'], 318 | 'login' => $form_state['values']['owner_login'], 319 | 'password' => $form_state['values']['owner_password'], 320 | ), 321 | 'domain' => $form_state['values']['domain_name'], 322 | 'port' => $form_state['values']['port'], 323 | 'address' => $form_state['values']['origin_ip'], 324 | 'ipv4' => $form_state['values']['origin_ip'], 325 | 'version' => 1, 326 | 'id' => 'syslog', 327 | ); 328 | 329 | $api = fastly_get_api(); 330 | 331 | $account = $api->signup($data); 332 | 333 | if (isset($account->msg)) { 334 | drupal_set_message($account->msg, 'error'); 335 | } 336 | else { 337 | variable_set('fastly_used_registration', 1); 338 | variable_set('fastly_api_key', $account->api_key); 339 | variable_set('fastly_service_id', $account->service_id); 340 | 341 | drupal_set_message(t('Registration successful! You will receive a confirmation email very soon. Please check your inbox and verify your account by clicking the received link.')); 342 | 343 | drupal_goto('admin/config/services/fastly/purge'); 344 | } 345 | } 346 | 347 | /** 348 | * Implements hook_form_submit(). 349 | */ 350 | function fastly_new_service_form_submit($form, &$form_state) { 351 | $data = array( 352 | 'name' => $form_state['values']['name'], 353 | 'domain' => $form_state['values']['domain_name'], 354 | 'port' => $form_state['values']['port'], 355 | 'address' => $form_state['values']['domain_name'], 356 | 'ipv4' => $form_state['values']['origin_ip'], 357 | 'version' => 1, 358 | 'id' => 'syslog', 359 | ); 360 | 361 | $api = fastly_get_api(); 362 | 363 | $service = $api->createService($data); 364 | 365 | if (isset($service->msg)) { 366 | drupal_set_message($service->msg, 'error'); 367 | } 368 | else { 369 | variable_set('fastly_service_id', $service->id); 370 | 371 | drupal_set_message(t('A new service successfuly created and set to default.')); 372 | 373 | drupal_goto('admin/config/services/fastly'); 374 | } 375 | } 376 | 377 | /** 378 | * Implements hook_form_submit(). 379 | */ 380 | function fastly_purge_form_submit($form, &$form_state) { 381 | $method = $form_state['triggering_element']['#name']; 382 | $api = fastly_get_api(); 383 | 384 | switch ($method) { 385 | case 'all': 386 | $api->purgeAll(); 387 | break; 388 | 389 | case 'urls': 390 | $values = trim($form_state['values']['urls_list']); 391 | 392 | if (empty($values)) { 393 | $error = (bool) drupal_set_message(t('Please input the URLs to purge.'), 'error'); 394 | } 395 | else { 396 | foreach (explode("\n", $values) as $line) { 397 | $api->purgePath(trim($line)); 398 | } 399 | } 400 | break; 401 | 402 | case 'keys': 403 | $values = trim($form_state['values']['keys_list']); 404 | 405 | if (empty($values)) { 406 | $error = (bool) drupal_set_message(t('Please input the keys to purge.'), 'error'); 407 | } 408 | else { 409 | foreach (explode("\n", $values) as $line) { 410 | $api->purgeKey(trim($line)); 411 | } 412 | } 413 | break; 414 | } 415 | 416 | if (empty($error)) { 417 | drupal_set_message(t('Cache successfuly purged.')); 418 | } 419 | } 420 | 421 | /** 422 | * A reusable snippet. 423 | * 424 | * Checks for error messages 425 | * and sets up an extra message with a link to Zendesk. 426 | */ 427 | function fastly_help_message() { 428 | if (drupal_get_messages('error', FALSE)) { 429 | drupal_set_message(t('Click here if you need help.'), 'warning'); 430 | } 431 | } 432 | --------------------------------------------------------------------------------