Skip to content

Instantly share code, notes, and snippets.

@mogilka
Last active March 7, 2017 16:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mogilka/56a97958a6062dbb767e30261314e3d3 to your computer and use it in GitHub Desktop.
Save mogilka/56a97958a6062dbb767e30261314e3d3 to your computer and use it in GitHub Desktop.
PHP: How to make Web widget with limited (authorized) access
<?php
/**
* Client widget model
* @todo for Client and Widget entities you should create appropriate models
*/
class ClientWidget {
public static function getTable() {
return 'client_widget';
}
/**
* Find active customer widget
* @param string $apikey customer ID
* @param string $action
* @return self|NULL
*/
public static function getActive($apikey, $action) {
$key = __CLASS__."getActive".$apikey."_".$action;
$model = $cache->get($key);
if (false === $model) {
$w = Widget::getTable();
$cw = self::getTable();
$sql = "select * from $cw
inner join $w on $cw.widgetid = $w.id
where $cw.apikey = :apikey
and $w.action = :action
and $cw.initdate <= :today
and $cw.finaldate >= :today
limit 1";
$model = self::find($sql, [
":apikey"=>$apikey,
":action"=>$action,
":today"=>date("Y-m-d")]);
$cache->set($key, $model);
}
return $model;
}
}
CREATE TABLE `client` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`domain` varchar(32) NOT NULL,
`email` varchar(128) NOT NULL,
`initdate` date DEFAULT NULL,
`finaldate` date DEFAULT NULL,
`apikey` varchar(16) NOT NULL,
`document` varchar(45) DEFAULT NULL,
`created_at` varchar(45) NOT NULL,
PRIMARY KEY (`id`),
KEY `apikey` (`apikey`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `client` VALUES (1,'Customer','https://secret-santa.gift https://www.secret-santa.gift','info@customer.mail','2017-01-10','2017-12-31','123456789','Contract 01/17','2017-01-10 16:48');
CREATE TABLE `widget` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`description` text NOT NULL,
`template` varchar(255) NOT NULL DEFAULT '0',
`action` varchar(32) NOT NULL,
`created_at` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `widget` VALUES (1,'Moon phase','','https://zvezdochet.guru/widget/moon?key=123456789&date=2017-01-10&zone=6','moon','2017-01-10 14:37');
CREATE TABLE `client_widget` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`widgetid` bigint(20) NOT NULL,
`initdate` date NOT NULL,
`finaldate` date NOT NULL,
`apikey` varchar(16) NOT NULL,
`created_at` varchar(45) NOT NULL,
PRIMARY KEY (`id`),
KEY `widgetid` (`widgetid`),
KEY `apikey` (`apikey`),
CONSTRAINT `client_widget` FOREIGN KEY (`widgetid`) REFERENCES `widget` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `widget_client` FOREIGN KEY (`apikey`) REFERENCES `client` (`apikey`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `client_widget` VALUES (1,1,'2017-01-20','2017-12-31','123456789','2017-01-10 14:37');
<?php
/**
* Renders action content to be placed within iframe
* @todo you should create appropriate CSS style
*/
* @var $date
* @var $zone
* @var $brand true|false
* @var $style
*/
//if widget has links, it must open them out of iframe
$target = "";
if ($brand) {
$target = 'target="_top"'; ?>
<link href="https://zvezdochet.guru/css/widget-<?=$style?>.css" rel="stylesheet">
<?php } ?>
<div class="vegetable">
<?php #some HTML content ?>
<?php if ($brand): ?>
<div class="watermark-bottom">
<a href="https://zvezdochet.guru" <?=$target?>>Stargazer</a>
</div>
<?php endif; ?>
</div>
/**
* Get widget content to be placed within iframe.
* May be used for different widgets
*/
(function() {
try {
var obj = JSON.parse(decodeURIComponent(document.uriparams));
var params = Object.keys(obj).map(function(key) {
return key + '=' + obj[key];
}).join('&');
var url = "https://zvezdochet.guru/widget/" + document.uriaction + "?" + params;
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (XMLHttpRequest.DONE == req.readyState) {
if (200 == req.status)
document.write(req.responseText);
}
};
req.open("GET", url, true);
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
req.send();
} catch(e) {
console.log(e.message);
}
})()
<!--
customer website page
parameter 'type' specifies widget controller action
-->
<script src="https://zvezdochet.guru/widget.js"></script>
<div id="widget-content">
<script type="text/javascript">
Stargazer.widget.init({
"containerid":"widget-content",
"style":"default",
"fontsize":"small",
"width":300,
"height":500,
"id":"moon-widget",
"type":"moon",
"key":"123456789",
"date":"2017-02-26",
"zone":6
});</script>
</div>
/**
* Create iframe and dynamically fill it with external JS content.
* This approach ensures asynchronous data loading on customer website.
* May be used for different widgets
*/
function compose(d, iparams) {
var p = "https:" === window.location.protocol ? "https:" : "http:",
src = p + "//zvezdochet.guru/request.js";
var iframe = d.createElement('iframe');
iframe.id = iparams.id;
iframe.width = iparams.width;
iframe.height = iparams.height;
iframe.scrolling = "auto";
iframe.frameborder = "0";
iframe.style="border:0";
d.getElementById(iparams.containerid).appendChild(iframe);
var html = '<body onload="var d=document;' +
'd.getElementsByTagName(\'head\')[0].appendChild(d.createElement(\'script\')).src=\'' + src + '\'">';
var doc = iframe.contentWindow.document;
doc.uriaction = iparams.type;
doc.uriparams = iparams.uri;
doc.open().write(html);
doc.close();
}
window.Stargazer = window.Stargazer || {};
window.Stargazer.widget = {
init: function(params) {
params = params || {};
compose(document, {
width: params.width,
height: params.height,
type: params.type,
id: params.id,
containerid: params.containerid,
uri: encodeURIComponent(JSON.stringify(params))
});
}
};
<?php
/**
* @todo you may want to manage created models, so you'll need appropriate controllers and views which I ain't cover in my gist
*/
class WidgetController {
/**
* Moon phase widget
* @param string $key customer ID
* @param string $date date
* @param number $zone timezone
* @param string $style widget style
* @param string $fontsize font size small|medium|large
*/
public function actionMoon($key, $date = null, $zone = 0, $style = "default", $fontsize = "medium") {
if (empty($key))
return "invalid key";
$widget = ClientWidget::getActive($key, "moon");
if (empty($widget))
return "";
else {
$this->initHeaders($widget);
if (empty($date))
$date = date("Y-m-d");
$cachekey = __CLASS__."actionMoon".$date."_".$zone."_".$style;
$partial = $cache->get($cachekey);
if (false === $partial) {
$partial = $this->render('moon', [
"date"=>$date,
"zone"=>$zone,
"brand"=>true,
"style"=>$style
]);
$cache->set($cachekey, $partial, 42300);
}
return $partial;
}
}
/**
* Initialize headers for customer response.
* Noone but your customer can access widget content due to CORS.
* The customer can access widget from all domains specified in its profile
* @param Widget $widget
*/
private function initHeaders($widget) {
$headers = $response->headers;
$headers->add('Access-Control-Allow-Methods', 'GET,OPTION');
$headers->add('Access-Control-Allow-Headers', 'Origin, Content-Type, X-Auth-Token, X-Requested-With');
$http_origin = $_SERVER['HTTP_ORIGIN'];
$domains = explode(" ", $widget->client->domain);
foreach ($domains as $domain) {
if ($http_origin == $domain) {
$headers->add('Access-Control-Allow-Origin', $http_origin);
break;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment