SMF Community Helpers > Customization Development

[MOD][WIP]Accelerated Mobile Pages

(1/4) > >>

nend:
Giving this away to anyone that wants it.

Sources/Amp.php

--- Code: ---<?php

if (!defined('SMF'))
die('Hacking attempt...');

function AmpMain() {
if (isset($_REQUEST['t']))
AmpTopic($_REQUEST['t']);
elseif (isset($_REQUEST['m']))
AmpMedia($_REQUEST['m']);
else
si404();
}

function AmpTopic($id) {

global $context, $smcFunc, $scripturl, $boardurl, $modSettings;

loadTemplate('Amp');
$context['template_layers'] = array('html');
$context['sub_template'] = 'AmpTopic';
$modSettings['disableQueryCheck'] = 1;
$request = $smcFunc['db_query']('', '
SELECT
m.poster_time, m.subject, m.id_topic, m.id_member, m.id_msg, b.id_board, b.name AS board_name,
IFNULL(mem.real_name, m.poster_name) AS poster_name, m.body, b.thumb_board as thumb, m.modified_time,
(SELECT id_attach FROM {db_prefix}attachments WHERE id_thumb != 0 AND {db_prefix}attachments.id_msg = m.id_msg ORDER BY id_attach LIMIT 1) AS id_attach
FROM {db_prefix}topics AS t
INNER JOIN {db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)
INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)
LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
WHERE {query_wanna_see_board} AND t.id_topic = {int:id}
LIMIT 1' ,
array(
'id' => (int) $id
)
);
$context['amp'] = $smcFunc['db_fetch_assoc']($request);

if ($context['amp']) {
$context['amp']['subject'] = censorText($context['amp']['subject']);
$context['page_title'] = $context['amp']['subject'];
$context['canonical'] = $scripturl . '?topic=' . $context['amp']['id_topic'];
$context['posted'] = date('Y-m-d\TH:i:s', $context['amp']['poster_time']);
$context['modified'] = date('Y-m-d\TH:i:s', empty($context['amp']['modified_time']) ? $context['amp']['poster_time'] : $context['amp']['modified_time']);
$context['head_img'] = false;
if (preg_match('[smg id=([0-9]+)(.*)]', $context['amp']['body'], $match))
$context['head_img'] = $context['rel_img'] = $scripturl.'?action=media;sa=media;in='.$match[1].';preview';
elseif (preg_match('#\[img(.*)\](.*)\[\/img\]#Ui', $context['amp']['body'], $match))
$context['head_img'] = $context['rel_img'] = $match[2];
elseif (isset($context['amp']['id_attach']) && $context['amp']['id_attach'] != 0)
$context['head_img'] = $context['rel_img'] = $scripturl . '?action=dlattach;topic='.$context['amp']['id_topic'].';attach='.$context['amp']['id_attach'].';image';
elseif (isset($context['amp']['thumb']) && $context['amp']['thumb'] != '')
$context['rel_img'] = $boardurl.'/img/b/sm/'.$context['amp']['thumb'];
else
$context['rel_img'] = $boardurl.'/img/viral/site_thumb.png';

$context['amp']['body'] = amp_rewrite(parse_bbc($context['amp']['body']));
} else
si404();
}

function AmpMedia($id) {

global $context, $smcFunc, $scripturl, $boardurl;

loadTemplate('Amp');
$context['template_layers'] = array('html');
$context['sub_template'] = 'AmpMedia';

$request = $smcFunc['db_query']('','
SELECT m.id_media, m.id_member, m.title, m.description, m.time_added, m.time_added AS posted_on, m.id_preview, m.type,
IFNULL(mem.real_name, m.member_name) AS poster_name, m.id_thumb, a.name AS album_name, m.album_id, m.last_edited
FROM {db_prefix}aeva_media as m
INNER JOIN {db_prefix}aeva_albums as a ON (a.id_album = m.album_id)
LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = m.id_member)
WHERE FIND_IN_SET(-1, a.access) AND m.id_media = {int:id}
ORDER BY m.time_added DESC
LIMIT 1',
array(
'id' => (int) $id
)
);
$context['amp'] = $smcFunc['db_fetch_assoc']($request);

if ($context['amp']) {
$context['amp']['subject'] = censorText($context['amp']['title']);
$context['page_title'] = $context['amp']['title'];
$context['canonical'] = $scripturl . '?action=media;sa=item;in=' . $context['amp']['id_media'];
$context['posted'] = date('Y-m-d\TH:i:s', $context['amp']['posted_on']);
$context['modified'] = date('Y-m-d\TH:i:s', empty($context['amp']['last_edited']) ? $context['amp']['posted_on'] : $context['amp']['last_edited']);
$context['amp']['body'] = amp_rewrite(parse_bbc($context['amp']['description']));
$context['rel_img'] = empty($context['amp']['id_preview']) ? $boardurl.'/img/viral/site_thumb.png' : $scripturl.'?action=media;sa=media;in='.$context['amp']['id_media'].';preview';
$context['head_img'] = empty($context['amp']['id_preview']) ? false : $scripturl.'?action=media;sa=media;in='.$context['amp']['id_media'].';preview';
} else
si404();

}

function amp_rewrite($data) {

/*
 * This is resource intensive, especially since we parse the BCC and
 * now we are doing more just to make sure the markup is up to AMP standard.
 * Good thing it is just one post.
 */

// This does happen, empty media descriptions, so we return a empty string.
if (empty($data))
return('');

// Lets use the DOM first, the server will thank us for it.
libxml_use_internal_errors(true);
$DOM = new DOMDocument();
$DOM->loadHTML($data);
$XPath = new DOMXPath($DOM);

// Got class but no style, no borders no limits :P
foreach(array('style','border') as $attribute)
foreach($XPath->query('//*[@'.$attribute.']') as $item)
$item->removeAttribute($attribute);

// Unallowed stuff, button is allowed but figured it looked ugly with it and without the other stuff
foreach(array('script','frame','frameset','object','param','applet','embed','form','input','textarea','select','option','button','iframe','video','audio') as $item)
while($node = $XPath->query('//'.$item)->item(0))
$node->parentNode->removeChild($node);

// Put the stuff back.
$data = $DOM->saveHTML();

// Other stuff below, <amp- is not valid HTML, that is why we can't do it in the DOM. :(

//Some stuff needs to be done one at a time, borrowed from SMF.
$pos = -1;
while ($pos !== false){
$last_pos = isset($last_pos) ? max($pos, $last_pos) : $pos;
$pos = strpos($data, '<', $pos + 1);
if ($pos === false || $last_pos > $pos)
$pos = strlen($data) + 1;
if ($last_pos < $pos - 1){
$last_pos = max($last_pos, 0);
$str = substr($data, $last_pos, $pos - $last_pos);

// Image is *
$str = preg_replace('~<img (.*?)class="(emoji|smiley)\b.*?>~si', '<amp-img width="30" height="30" $1></amp-img>', $str);
$str = preg_replace('~<img (.*?)>~si', '<center><amp-img width="300" height="200" $1></amp-img></center>', $str);

if ($str != substr($data, $last_pos, $pos - $last_pos)){
$data = substr($data, 0, $last_pos) . $str . substr($data, $pos);
$old_pos = strlen($data) + $last_pos;
$pos = strpos($data, '<', $last_pos);
$pos = $pos === false ? $old_pos : min($pos, $old_pos);
}
}
if ($pos >= strlen($data) - 1)
break;
}

// This could be better, when I get the chance to write the rest.
// This requires https. :(
/* $ctamp = array('video','audio','iframe');
foreach ($ctamp as $item)
$data = str_replace('<'.$item, '<amp-'.$item, $data);
*/
return $data;
}
?>

--- End code ---

/Themes/???/Amp.template.php

--- Code: ---<?php

function template_html_above(){

global $scripturl, $context, $boardurl;
$context['ob_rewrite_compat'] = true;

echo '
<!doctype html>
<html amp lang="en">
<head>
<meta charset="', strtolower($context['character_set']), '">
<script async src="https://cdn.ampproject.org/v0.js"></script>
<title>', $context['page_title'], '</title>
<link rel="canonical" href="', xpath_urls_filter($context['canonical']), '" />
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
<script type="application/ld+json">{
"@context": "http://schema.org",
"@type": "Article",
"mainEntityOfPage":{"@type":"WebPage","@id":"', xpath_urls_filter($context['canonical']), '"},
"headline": "', $context['page_title'], '",
"datePublished": "', $context['posted'], '",
"dateModified":"', $context['modified'], '",
"image":{"@type": "ImageObject","url": "'.xpath_urls_filter($context['rel_img']).'","width": "auto","height": "auto"},
"author":{"@type":"Person","name":"'.$context['amp']['poster_name'].'"},
"publisher":{"@type": "Organization","name":"SI Community","logo":{"@type":"ImageObject","url":"'.$boardurl.'/img/viral/site_thumb.png","width":"auto","height":"auto"}}
}</script>
<script async custom-element="amp-ad" src="https://cdn.ampproject.org/v0/amp-ad-0.1.js"></script>
<script async custom-element="amp-social-share" src="https://cdn.ampproject.org/v0/amp-social-share-0.1.js"></script>
<script async custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.1.js"></script>
<script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
<style amp-custom>.logo {background-color:#28D;font-variant:small-caps;color:#ffc;text-align:left;padding:6px;box-shadow:0 1px 2px rgba(10,10,10,0.4)}.logo > a {color:#DEF;font:1.1em trebuchet,Helvetica,sans-serif;font-weight: bold;text-decoration:none}.logo amp-img{float:left}.heading {padding:5px;margin-top:0.83em}.who, .who a{color:#999;text-decoration:none}.sep{border:none;height:1px;background-color:#aaa;width:100px;}h3 a{text-decoration:none;color:#000;}</style>
<style amp-boilerplate>body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate>body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
</head>
<body>';
}

function template_html_below() {
echo '
<p class="heading">
<amp-social-share type="twitter" width="45" height="33"></amp-social-share><amp-social-share type="facebook" width="45" height="33"></amp-social-share><amp-social-share type="gplus" width="45" height="33"></amp-social-share><amp-social-share type="email" width="45" height="33"></amp-social-share><amp-social-share type="pinterest" width="45" height="33"></amp-social-share>
</p>
<amp-analytics type="googleanalytics">
<script type="application/json">{"vars": {"account": "UA-10276350-3"}}</script>
</amp-analytics>
</body>
</html>';
}

function template_AmpTopic(){

global $context, $scripturl, $boardurl;

echo '
<div class="logo">
<a href="'.xpath_urls_filter($scripturl).'"><amp-img src="'.$boardurl.'/img/viral/site_thumb.svg" alt="SI Community" title="SI Community" height="20px" width="20px"></amp-img></a>
&nbsp;&nbsp;<a href="'.xpath_urls_filter($scripturl.'?board=' . $context['amp']['id_board']).'">' . $context['amp']['board_name'] . '</a>
</div>
'.($context['head_img'] ? '<a href="'.xpath_urls_filter($scripturl.'?topic='.$context['amp']['id_topic'].';topicseen#new').'"><amp-img width="600" height="400" layout="responsive" src="'.xpath_urls_filter($context['rel_img']).'"></amp-img></a>' : '').'
<div class="heading">
<center>
<a href="'.xpath_urls_filter($scripturl).'"><amp-img src="'.$boardurl.'/img/viral/site_thumb50.png" alt="SI Community" title="SI Community" height="50px" width="50px"></amp-img></a>
<h3><a href="'.xpath_urls_filter($scripturl.'?topic='.$context['amp']['id_topic'].';topicseen#new').'">'.$context['page_title'].'</a></h3>
<hr class="sep">
<span>
<b>SI Community</b><br>
<small class="who">
<a href="'.xpath_urls_filter($scripturl.'?action=profile;u=' . $context['amp']['id_member']).'">' . $context['amp']['poster_name'] . '</a> • 
'.date('M j, Y', $context['amp']['poster_time']).'
</small>
</span>
</center>
</div>
<div class="heading">
'.xpath_rewrite_buffer($context['amp']['body'], true, true).'
<center>
<div class="amp-ad-container">
<amp-ad width=300 height=250 type="adsense" data-ad-client="ca-pub-3272970468196826" data-ad-slot="4059282756"></amp-ad>
</div>
</center>
<p><a href="'.xpath_urls_filter($scripturl.'?topic='.$context['amp']['id_topic'].';topicseen#new').'"><button>Read More</button></a></p>
</div>';
}

function template_AmpMedia(){

global $context, $scripturl, $boardurl;

echo '
<div class="logo">
<a href="'.xpath_urls_filter($scripturl).'"><amp-img src="'.$boardurl.'/img/viral/site_thumb.svg" alt="SI Community" title="SI Community" height="20px" width="20px"></amp-img></a>
&nbsp;&nbsp;<a href="'.xpath_urls_filter($scripturl.'?action=media;sa=album;in='.$context['amp']['album_id']).'">' . $context['amp']['album_name'] . '</a>
</div>';

if ($context['head_img'])
echo '
<a href="'.xpath_urls_filter($scripturl.'?action=media;sa=item;in='.$context['amp']['id_media']).'"><amp-img width="600" height="400" layout="responsive" src="'.xpath_urls_filter($context['rel_img']).'"></amp-img></a>';
/* else
if ($context['amp']['type'] == 'audio' || $context['amp']['type'] == 'video')
echo '
<center><amp-video width="400" height="300" src="'.$scripturl.'?action=media;sa=media;in='.$context['amp']['id_media'].';v">
<div fallback>
<p>Your browser doesn’t support HTML5 audio/video</p>
</div>
</amp-video></center>';*/ //Need HTTPS :(

echo '
<div class="heading">
<center>
<a href="'.xpath_urls_filter($scripturl).'"><amp-img src="'.$boardurl.'/img/viral/site_thumb50.png" alt="SI Community" title="SI Community" height="50px" width="50px"></amp-img></a>
<h3><a href="'.xpath_urls_filter($scripturl.'?action=media;sa=item;in='.$context['amp']['id_media']).'">'.$context['page_title'].'</a></h3>
<hr class="sep">
<span>
<b>SI Community</b><br>
<small class="who">
<a href="'.xpath_urls_filter($scripturl .'?action=profile;u='.$context['amp']['id_member']).'">' . $context['amp']['poster_name'] . '</a> • 
'.date('M j, Y', $context['amp']['posted_on']).'
</small>
</span>
</center>
</div>
<div class="heading">
'.xpath_rewrite_buffer($context['amp']['body'], true, true).'
<center>
<div class="amp-ad-container">
<amp-ad width=300 height=250 type="adsense" data-ad-client="ca-pub-3272970468196826" data-ad-slot="4059282756"></amp-ad>
</div>
</center>
<p><a href="'.xpath_urls_filter($scripturl.'?action=media;sa=item;in='.$context['amp']['id_media']).'"><button>View Item</button></a></p>
</div>';
}
?>

--- End code ---

Add to Who.english.php

--- Code: ---$txt['whoamp_topic'] = 'Viewing AMP topic <a href="' . $scripturl . '?action=amp;t=%1$d">%2$s</a>.';
$txt['whoamp_media'] = 'Viewing AMP media <a href="' . $scripturl . '?action=amp;m=%1$d">%2$s</a>.';
$txt['whoamp_unknown'] = 'Viewing unknown AMP document.';
--- End code ---

Find in /Sources/Who.php

--- Code: --- // Unlisted or unknown action.
else
$data[$k] = $txt['who_unknown'];
--- End code ---
Add Before

--- Code: --- elseif ($actions['action'] == 'amp')
{
if (isset($actions['t'])) {
$msgid = (int) (isset($actions['t']) ? $actions['t'] : 0);
$result = $smcFunc['db_query']('', '
SELECT m.id_topic, m.subject
FROM {db_prefix}messages AS m
INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board)
INNER JOIN {db_prefix}topics AS t ON (t.id_topic = m.id_topic' . ($modSettings['postmod_active'] ? ' AND t.approved = {int:is_approved}' : '') . ')
WHERE m.id_msg = {int:id_msg}
AND {query_see_board}' . ($modSettings['postmod_active'] ? '
AND m.approved = {int:is_approved}' : '') . '
LIMIT 1',
array(
'is_approved' => 1,
'id_msg' => $msgid,
)
);
list ($id_topic, $subject) = $smcFunc['db_fetch_row']($result);
$data[$k] = sprintf($txt['whoamp_topic'], $id_topic, $subject);
$smcFunc['db_free_result']($result);
if (empty($id_topic))
$data[$k] = $txt['whoamp_unknown'];
} elseif (isset($actions['m'])) {
$mid = (int) (isset($actions['m']) ? $actions['m'] : 0);
$result = $smcFunc['db_query']('', '
SELECT m.id_media, m.title
FROM {db_prefix}aeva_media AS m
INNER JOIN {db_prefix}aeva_albums as a ON (a.id_album = m.album_id)
WHERE FIND_IN_SET(-1, a.access) AND m.id_media = {int:id_media}
LIMIT 1',
array(
'id_media' => $mid,
)
);
list ($id_media, $subject) = $smcFunc['db_fetch_row']($result);
$data[$k] = sprintf($txt['whoamp_media'], $id_media, $subject);
$smcFunc['db_free_result']($result);
if (empty($id_media))
$data[$k] = $txt['whoamp_unknown'];
} else
$data[$k] = $txt['whoamp_unknown'];
}
--- End code ---

Find in /Sources/Load.php

--- Code: --- $simpleActions = array(
'findmember',
'helpadmin',
'printpage',
'quotefast',
'spellcheck',
--- End code ---
Add After

--- Code: --- 'amp',
--- End code ---

Add to action array in index.php or use a hook

--- Code: --- 'amp' => array('Amp.php', 'AmpMain'),
--- End code ---

You'll have to add the canonical to the topic page.
The media part is for Aeva Media, it can be removed.
You'll have to remove all the xpath stuff as that is a custom function for my site only.

Rock Lee:
To Accelerate in load times? I fail to understand it all, excuse my English is not all good!


Regards!

rickey29:
Hi nend,

Thanks for your sharing!

Is it a good if we combine your code with some mobile-only theme, instead of default Simple Machines theme?

Thanks,

Rickey

nend:
The code isn't really theme dependent. You can change aspects of the CSS in the code to make it more appealing or to match the theme of your website, but it is more like another markup.

Think of it like your RSS feed. You can change how data is displayed, but in the end it's mainly up to the reader, in AMP's case it's script interpreter to determine how the page is displayed.

Also keep in mind the canonical is required, the AMP document must link to the full document and visa versa. AMP validation is a real pain, but if you strip everything down to almost bare bones and use their special markup you shouldn't have too much trouble incorporating it.

gorbi:
mod is ready?

Navigation

[0] Message Index

[#] Next page

Go to full version