webserver/article.php (165 lines of code) (raw):

<?php include('vendor/autoload.php'); use Facebook\InstantArticles\Transformer\Transformer; use Facebook\InstantArticles\Elements\InstantArticle; use Facebook\InstantArticles\Elements\Header; use Facebook\InstantArticles\AMP\AMPArticle; $mc; define("CACHE_KEY_TYPE_IA", "ia"); define("CACHE_KEY_TYPE_AMP", "amp"); define("CACHE_KEY_TYPE_URL", "url"); define("CACHE_KEY_TYPE_WARNINGS", "warnings"); define("CACHE_KEY_HASH_ALGO", "sha256"); function exception_error_handler($errno, $errstr, $errfile, $errline ) { // noop } function try_init_cache() { if (class_exists(Memcached)) { global $mc; $mc = new Memcached(); $mc->addServer("memcached", 11211); } } function try_get_cached_value($key, $cache_type) { if (!class_exists(Memcached)) return null; // Need to create a valid key for memcached $hashed_key = hash(CACHE_KEY_HASH_ALGO, $cache_type . ": " . $key, false); global $mc; $value = $mc->get($hashed_key); if (!$value) return null; return $value; } function try_set_cached_value($key, $value, $cache_type){ if (!class_exists(Memcached)) return; // Need to use a valid key for memcached $hashed_key = hash(CACHE_KEY_HASH_ALGO, $cache_type . ": " . $key, false); global $mc; $mc->set($hashed_key, $value); } function get_html_markup($url) { $cache_key = $url; $possible_html_markup = try_get_cached_value($cache_key, CACHE_KEY_TYPE_URL); // Cache hit if (!is_null($possible_html_markup)) return $possible_html_markup; // Cache miss $context_options = stream_context_create(array( "ssl"=>array( "verify_peer"=>false, "verify_peer_name"=>false, ), 'http'=>array( 'header'=>"User-Agent: facebookexternalhit/1.1\r\n" ), )); // Fetch the URL //-------------- $content = file_get_contents($url, false, $context_options); try_set_cached_value($cache_key, $content, CACHE_KEY_TYPE_URL); return $content; } function get_instant_article($html_markup, $url, $rules, &$response) { $cache_key = $rules . $url; $possible_instant_article = try_get_cached_value($cache_key, CACHE_KEY_TYPE_IA); // Cache hit if (!is_null($possible_instant_article)) return $possible_instant_article; // Cache miss // Load rules //----------- $transformer = new Transformer(); $transformer->loadRules($rules); // Transform //---------- $instant_article = InstantArticle::create(); $transformer->transformString($instant_article, $html_markup); $warnings = $transformer->getWarnings(); $warnings_func = function($warning) { $warning_obj = new stdClass(); $warning_obj->message = $warning->__toString(); if (method_exists($warning,'getSelector')) { $warning_obj->selector = $warning->getSelector() ?: null; } if (method_exists($warning,'getFields')) { $warning_obj->field = $warning->getFields() ?: null; } return $warning_obj; }; $warnings = array_unique(array_map($warnings_func, $warnings), SORT_REGULAR); $response->warnings = $warnings; try_set_cached_value($cache_key, $warnings, CACHE_KEY_TYPE_WARNINGS); try_set_cached_value($cache_key, $instant_article, CACHE_KEY_TYPE_IA); return $instant_article; } function get_amp_markup($instant_article, $url, $rules) { $cache_key = $rules . $url; $possible_amp_markup = try_get_cached_value($cache_key, CACHE_KEY_TYPE_AMP); // Cache hit if (!is_null($possible_amp_markup)) return $possible_amp_markup; // Cache miss $properties = array( 'styles-folder' => __DIR__.'/styles/' // Where the styles are stored ); $amp_article = AMPArticle::create($instant_article, $properties); $amp_article->getObserver()->addFilter( 'AMP_HEAD', function ($head) { $style_node = $head->ownerDocument->createElement('link'); $style_node->setAttribute('rel', 'stylesheet'); $style_node->setAttribute('href', 'style.css'); $head->appendChild($style_node); return $head; } ); $amp_markup = $amp_article->render(); try_set_cached_value($cache_key, $amp_markup, CACHE_KEY_TYPE_AMP); return $amp_markup; } set_error_handler("exception_error_handler"); try_init_cache(); try { // Read the URL parameter //---------------------- $url = $_GET['url']; $rules = $_POST['rules']; if (!$url || !$rules || filter_var($url, FILTER_VALIDATE_URL) === FALSE) { $response->error = invalidIA(); header('Content-type: application/json'); echo json_encode($response); die(); } $html_markup = get_html_markup($url); $instant_article = get_instant_article($html_markup, $url, $rules, $response); $response->source = $instant_article->render(null, true); if ($instant_article->isValid()) { $amp_markup = get_amp_markup($instant_article, $url, $rules); if ($amp_markup) { $response->amp = $amp_markup; } } else { $response->error = invalidIA(); } header('Content-type: application/json'); echo json_encode($response); die(); } catch (Exception $e) { echo $e->getMessage(); echo $stacktrace = $e->getTraceAsString(); die(); } function invalidIA () { return <<<HTML <!doctype html> <html> <body> <p> Open an article, then connect the required fields in the <em>Article</em> element to see a preview. </p> <style> html { height: 100%; } body { display: flex; align-items: center; font-family: sans-serif; color: #ccc; height: 100%; } p { max-width: 300px; margin: auto; text-align: center; } </style> </body> </html> HTML; }