Skip to content
Browse files

initial commit

  • Loading branch information...
0 parents commit 4f9b2385d282070ca8aac05d7f9935280350c80c @zagraves zagraves committed Sep 22, 2009
Showing with 676 additions and 0 deletions.
  1. +92 −0 yupdates.php
  2. +75 −0 yupdates_db.php
  3. +47 −0 yupdates_hooks.php
  4. +158 −0 yupdates_menu.php
  5. +50 −0 yupdates_options.php
  6. +70 −0 yupdates_sessionstore.php
  7. +76 −0 yupdates_utils.php
  8. +108 −0 yupdates_widgets.php
92 yupdates.php
@@ -0,0 +1,92 @@
+<?php
+/*
+Plugin Name: Yahoo! Updates
+Plugin URI: http://developer.yahoo.com/social/
+Description: A Yahoo! Updates plugin for WordPress.
+Version: 0.9
+Author: Yahoo! Inc.
+Author URI: http://www.yahoo.com/
+*/
+?>
+<?php
+ define("YUPDATES_WIDGET_ENABLED", true);
+
+ require_once("lib/OAuth/OAuth.php");
+ require_once("lib/Yahoo/YahooOAuthApplication.class.php");
+
+ require_once("yupdates_sessionstore.php");
+ require_once("yupdates_utils.php");
+ require_once("yupdates_menu.php");
+ require_once("yupdates_options.php");
+ require_once("yupdates_hooks.php");
+ require_once("yupdates_db.php");
+
+ if(YUPDATES_WIDGET_ENABLED) {
+ require_once("yupdates_widgets.php");
+ }
+
+ add_action("admin_menu", "yupdates_plugin_menu");
+ add_action("init", "yupdates_auth_init");
+
+ add_action("delete_post", "yupdates_delete_post");
+ add_action("edit_post", "yupdates_edit_post");
+ add_action("publish_post", "yupdates_publish_post");
+
+ $yupdates_session_store = NULL;
+?>
+<?php
+ function yupdates_plugin_menu() {
+ add_submenu_page("users.php", "Yahoo! Updates Page", "Yahoo! Updates Authorization", 0, "yupdates_menu", "yupdates_menu");
+ add_options_page("Yahoo! Updates Plugin Options", "Yahoo! Updates Plugin", 8, "yupdates_plugin_options", "yupdates_plugin_options");
+ }
+
+ function yupdates_auth_init() {
+ //
+ $session_store = yupdates_get_sessionStore();
+
+ //
+ $app_info = yupdatesdb_getApplicationInfo();
+
+ // fetch application keys from user options
+ $ck = get_option("yupdates_consumer_key");
+ $cks = get_option("yupdates_consumer_secret");
+ $appid = get_option("yupdates_application_id");
+
+ //
+ $application = new YahooOAuthApplication($ck, $cks, $appid);
+ $application_has_session = yupdates_has_session($application, $session_store);
+
+ // handle directions from auth flow
+ if(array_key_exists("yupdates_clearauthorization", $_REQUEST))
+ {
+ yupdates_clear_session();
+ }
+ else if(array_key_exists("auth_popup", $_REQUEST))
+ {
+ yupdates_close_popup();
+ }
+
+ //
+ if(!yupdatesdb_hasApplicationInfo() && stripos($_SERVER["REQUEST_URI"], "options-general.php?page=yupdates_plugin_options") === FALSE)
+ {
+ function yupdates_appinfo_warning() {
+ echo <<<HTML
+<div id="yupdates-appinfo-warning" class="updated fade"><p><strong>You haven't configured the Yahoo! Updates Plugin yet. <a href="options-general.php?page=yupdates_plugin_options">Configure the plugin.</a></strong></p></div>
+HTML;
+
+ }
+ add_action("admin_notices", "yupdates_appinfo_warning");
+ }
+ else if(yupdatesdb_hasApplicationInfo() && stripos($_SERVER["REQUEST_URI"], "users.php?page=yupdates_menu") === FALSE)
+ {
+ if(!$application_has_session) {
+ function yupdates_authorization_warning() {
+ echo <<<HTML
+<div id="yupdates-authorization-warning" class="updated fade"><p><strong>You haven't authorized the Yahoo! Updates Plugin yet. <a href="users.php?page=yupdates_menu">Authorize the plugin now.</a></strong></p></div>
+HTML;
+ }
+ add_action("admin_notices", "yupdates_authorization_warning");
+ }
+ }
+ }
+?>
75 yupdates_db.php
@@ -0,0 +1,75 @@
+<?php
+
+define("YUPDATES_USER_OPTION", "yupdates_updates_widget_users");
+define("YUPDATES_CONSUMER_KEY_OPTION", "yupdates_consumer_key");
+define("YUPDATES_CONSUMER_SECRET_OPTION", "yupdates_consumer_secret");
+define("YUPDATES_APPLICATION_ID_OPTION", "yupdates_application_id");
+define("YUPDATES_WIDGET_COUNT_OPTION", "yupdates_widget_count");
+define("YUPDATES_TITLE_TEMPLATE_OPTION", "yupdates_title_template");
+
+$updateUsers = get_option(YUPDATES_USER_OPTION);
+if(is_bool($updateUsers) && !$updateUsers) {
+ $updateUsers = array();
+}
+else if(!is_array($updateUsers)) {
+ $updateUsers = array();
+ delete_option(YUPDATES_USER_OPTION);
+}
+
+function yupdatesdb_hasApplicationInfo() {
+ return get_option(YUPDATES_CONSUMER_KEY_OPTION) &&
+ get_option(YUPDATES_CONSUMER_SECRET_OPTION) &&
+ get_option(YUPDATES_APPLICATION_ID_OPTION);
+}
+
+function yupdatesdb_getApplicationInfo() {
+ $info = array();
+ $info["ck"] = get_option(YUPDATES_CONSUMER_KEY_OPTION);
+ $info["cks"] = get_option(YUPDATES_CONSUMER_SECRET_OPTION);
+ $info["appid"] = get_option(YUPDATES_APPLICATION_ID_OPTION);
+ return $info;
+}
+
+function yupdatesdb_addUpdatesUser($user) {
+ global $updateUsers;
+ $updateUsers[$user] = true;
+ update_option(YUPDATES_USER_OPTION, $updateUsers);
+}
+
+function yupdatesdb_removeUpdatesUser($user) {
+ global $updateUsers;
+ $updateUsers[$user] = false;
+ update_option(YUPDATES_USER_OPTION, $updateUsers);
+}
+
+function yupdatesdb_isUpdatesUser($user) {
+ global $updateUsers;
+ return array_key_exists($user, $updateUsers) && $updateUsers[$user];
+}
+
+function yupdatesdb_listUpdatesUsers() {
+ global $updateUsers;
+
+ $users = array();
+ foreach($updateUsers as $user => $active) {
+ if($active) {
+ $users[] = $user;
+ }
+ }
+
+ return $users;
+}
+
+function yupdatesdb_getWidgetCount() {
+ $count = get_option(YUPDATES_WIDGET_COUNT_OPTION);
+ if(is_bool($count) || !is_numeric($count)) {
+ $count = 5;
+ }
+ return $count;
+}
+
+function yupdatesdb_setWidgetCount($count) {
+ update_option(YUPDATES_WIDGET_COUNT_OPTION, $count);
+}
+
+?>
47 yupdates_hooks.php
@@ -0,0 +1,47 @@
+<?php
+
+function yupdates_delete_post($postid) {
+
+}
+
+function yupdates_edit_post($postid) {
+
+}
+
+function yupdates_publish_post($postid) {
+ $session_store = yupdates_get_sessionStore();
+
+ $app_info = yupdatesdb_getApplicationInfo();
+
+ // fetch application keys from user options
+ $ck = get_option("yupdates_consumer_key");
+ $cks = get_option("yupdates_consumer_secret");
+ $appid = get_option("yupdates_application_id");
+ $title_template = get_option("yupdates_title_template");
+
+ $application = new YahooOAuthApplication($ck, $cks, $appid);
+ $application_has_session = yupdates_has_session($application, $session_store);
+
+ if($application_has_session) {
+ $post = get_post($postid);
+
+ $title_patterns = array('/#blog_title/', '/#blog_name/');
+ $title_replacements = array($post->post_title,get_bloginfo("name"));
+
+ $update = new stdclass();
+ $update->title = preg_replace($title_patterns, $title_replacements, $title_template);
+ $update->description = substr($post->post_content, 0, 256);
+ $update->link = get_bloginfo("url");
+
+ $response = $application->insertUpdate(null, $update->description, $update->title, $update->link);
+
+ // todo: better error handling
+ if(is_null($response)) {
+ error_log("Failed to generate Yahoo! Update for blog post.");
+ }
+ } else {
+ error_log('no session available');
+ }
+}
+
+?>
158 yupdates_menu.php
@@ -0,0 +1,158 @@
+<?php
+require_once("yosdk_lib5/OAuth/OAuth.php");
+require_once("yosdk_lib5/Yahoo/YahooOAuthApplication.class.php");
+require_once("WordPressSessionStore.php");
+
+function yupdates_menu() {
+ global $current_user;
+ get_currentuserinfo();
+
+ if(array_key_exists("yupdates_updateusers", $_REQUEST)) {
+ if($_REQUEST["yupdates_include_updates"]) {
+ yupdatesdb_addUpdatesUser($current_user->user_login);
+ }
+ else {
+ yupdatesdb_removeUpdatesUser($current_user->user_login);
+ }
+ }
+
+ // fetch application keys from user options
+ $ck = get_option("yupdates_consumer_key");
+ $cks = get_option("yupdates_consumer_secret");
+ $appid = get_option("yupdates_application_id");
+
+ $session_store = yupdates_get_sessionStore();
+
+ $application = new YahooOAuthApplication($ck, $cks, $appid);
+ $application_has_session = yupdates_has_session($application, $session_store);
+
+ $session = NULL;
+ $user = NULL;
+ $sharingUpdates = false;
+
+ if($application_has_session == false) {
+ $request_token = $session_store->fetchRequestToken();
+ $auth_url = ($request_token && $request_token->key) ? $application->getAuthorizationUrl($request_token) : "";
+ } else {
+ $sharingUpdates = yupdatesdb_isUpdatesUser($current_user->user_login);
+ }
+?>
+<div class="wrap">
+ <h2>Yahoo! Updates</h2>
+<?php if(!is_null($application) && $application_has_session) { ?>
+
+You have authorized the Yahoo! Updates plugin.
+<form method="post">
+
+<?php if(YUPDATES_WIDGET_ENABLED) { ?>
+
+ <p><label for="yupdates-include-updates">Include updates in widget? <input id="yupdates-include-updates" type="checkbox" name="yupdates_include_updates"<?php echo $sharingUpdates ? " checked='checked'" : "" ?>></label></p>
+ <input type="submit" name="yupdates_updateusers" value="Update">
+
+<?php } ?>
+
+ <input type="submit" name="yupdates_clearauthorization" value="Unauthorize">
+</form>
+
+<?php } else { ?>
+
+You have not yet authorized the Yahoo! Updates plugin.
+<p>
+ <input type="hidden" name="yupdates_authorize" value="true">
+ <input type="submit" value="Authorize" onclick="_yupdates_authorize();">
+</p>
+
+<?php } ?>
+
+</div>
+
+<script type="text/javascript">
+ var _gel = function(el) {return document.getElementById(el)};
+ var _yupdates_auth_url = "<?php echo $auth_url; ?>";
+ var _yupdates_authorize = function() {
+ if(_yupdates_auth_url != "") PopupManager.open(_yupdates_auth_url,600,435);
+ }
+</script>
+<script type="text/javascript">
+// a simplified version of step2 popuplib.js
+var PopupManager = {
+ popup_window:null,
+ interval:null,
+ interval_time:80,
+ waitForPopupClose: function() {
+ if(PopupManager.isPopupClosed()) {
+ PopupManager.destroyPopup();
+ window.location.reload();
+ }
+ },
+ destroyPopup: function() {
+ this.popup_window = null;
+ window.clearInterval(this.interval);
+ this.interval = null;
+ },
+ isPopupClosed: function() {
+ return (!this.popup_window || this.popup_window.closed);
+ },
+ open: function(url, width, height) {
+ this.popup_window = window.open(url,"",this.getWindowParams(width,height));
+ this.interval = window.setInterval(this.waitForPopupClose, this.interval_time);
+
+ return this.popup_window;
+ },
+ getWindowParams: function(width,height) {
+ var center = this.getCenterCoords(width,height);
+ return "width="+width+",height="+height+",status=1,location=1,resizable=yes,left="+center.x+",top="+center.y;
+ },
+ getCenterCoords: function(width,height) {
+ var parentPos = this.getParentCoords();
+ var parentSize = this.getWindowInnerSize();
+
+ var xPos = parentPos.width + Math.max(0, Math.floor((parentSize.width - width) / 2));
+ var yPos = parentPos.height + Math.max(0, Math.floor((parentSize.height - height) / 2));
+
+ return {x:xPos,y:yPos};
+ },
+ getWindowInnerSize: function() {
+ var w = 0;
+ var h = 0;
+
+ if ('innerWidth' in window) {
+ // For non-IE
+ w = window.innerWidth;
+ h = window.innerHeight;
+ } else {
+ // For IE
+ var elem = null;
+ if (('BackCompat' === window.document.compatMode) && ('body' in window.document)) {
+ elem = window.document.body;
+ } else if ('documentElement' in window.document) {
+ elem = window.document.documentElement;
+ }
+ if (elem !== null) {
+ w = elem.offsetWidth;
+ h = elem.offsetHeight;
+ }
+ }
+ return {width:w, height:h};
+ },
+ getParentCoords: function() {
+ var w = 0;
+ var h = 0;
+
+ if ('screenLeft' in window) {
+ // IE-compatible variants
+ w = window.screenLeft;
+ h = window.screenTop;
+ } else if ('screenX' in window) {
+ // Firefox-compatible
+ w = window.screenX;
+ h = window.screenY;
+ }
+ return {width:w, height:h};
+ }
+}
+</script>
+
+<?php
+}
+?>
50 yupdates_options.php
@@ -0,0 +1,50 @@
+<?php
+function yupdates_plugin_options() {
+ $ck = get_option('yupdates_consumer_key');
+ $cks = get_option('yupdates_consumer_secret');
+ $appid = get_option('yupdates_application_id');
+ $title_template = get_option('yupdates_title_template');
+
+ if($title_template == "") $title_template = "posted '#blog_title' on their WordPress blog '#blog_name'";
+?>
+<div class="wrap">
+ <h2>Yahoo! Updates Plugin Options</h2>
+
+ <p>In order to use the Yahoo! Updates plugin, you will need to register as an application developer on <a href="http://developer.apps.yahoo.com/dashboard/createKey.html" target="_blank">Yahoo!</a>. When signing up, make sure to select "This app requires access to private user data" and select "Read/Write" for Yahoo! Updates.</p>
+
+ <p>After completing the process, you will be given a consumer key, consumer secret and an application ID. Include those values below and save your changes. When done, visit the <a href="users.php?page=yupdates_menu">Yahoo! Updates Authorization page</a> to authorize the Yahoo! Updates plugin to access your Yahoo! Updates.</p>
+
+ <form method="post" action="options.php">
+ <?php
+ if(function_exists("wp_nonce_field")) {
+ wp_nonce_field('update-options');
+ }
+ ?>
+ <table class="form-table">
+ <tr valign="top">
+ <th scope="row">Yahoo! Consumer Key</th>
+ <td><input type="text" size=64 name="yupdates_consumer_key" value="<?php echo $ck; ?>" /></td>
+ </tr>
+ <tr valign="top">
+ <th scope="row">Yahoo! Consumer Secret</th>
+ <td><input type="text" size=64 name="yupdates_consumer_secret" value="<?php echo $cks; ?>" /></td>
+ </tr>
+ <tr valign="top">
+ <th scope="row">Yahoo! Application ID</th>
+ <td><input type="text" size=20 name="yupdates_application_id" value="<?php echo $appid; ?>" /></td>
+ </tr>
+ <tr valign="top">
+ <th scope="row">Your Update Title</th>
+ <td><input type="text" size=50 name="yupdates_title_template" value="<?php echo $title_template; ?>" /></td>
+ </tr>
+ </table>
+ <input type="hidden" name="action" value="update" />
+ <input type="hidden" name="page_options" value="yupdates_consumer_key,yupdates_consumer_secret,yupdates_application_id,yupdates_title_template" />
+ <p class="submit">
+ <input type="submit" name="Submit" value="<?php _e('Save Changes') ?>" />
+ </p>
+ </form>
+</div>
+<?
+}
+?>
70 yupdates_sessionstore.php
@@ -0,0 +1,70 @@
+<?php
+ class WordPressSessionStore {
+ var $optionName = NULL;
+ var $option = NULL;
+ var $consumerKey = "";
+
+ function WordPressSessionStore($user, $consumerKey) {
+ $this->optionName = sprintf("yupdates_tokens_%s", $user);
+ $this->consumerKey = $consumerKey;
+ $this->option = get_option($this->optionName);
+
+ if(is_bool($this->option) && !$this->option) {
+ $this->resetOption();
+ }
+
+ $this->validateConsumerKey();
+ }
+
+ function resetOption()
+ {
+ $this->option = array("rt" => NULL, "at" => NULL, "ck" => $this->consumerKey);
+ update_option($this->optionName, $this->option);
+ }
+
+ function validateConsumerKey()
+ {
+ if($this->consumerKey != $this->option["ck"]) {
+ $this->resetOption();
+ }
+ }
+
+ function hasRequestToken() {
+ return array_key_exists("rt", $this->option) && !is_null($this->option["rt"]);
+ }
+
+ function hasAccessToken() {
+ return array_key_exists("at", $this->option) && !is_null($this->option["at"]);
+ }
+
+ function storeRequestToken($token) {
+ $this->option["rt"] = base64_encode($token->to_string());
+ return update_option($this->optionName, $this->option);
+ }
+
+ function storeAccessToken($token) {
+ $this->option["at"] = base64_encode($token->to_string());
+ return update_option($this->optionName, $this->option);
+ }
+
+ function fetchRequestToken() {
+ $token_data = base64_decode($this->option["rt"]);
+ return YahooOAuthRequestToken::from_string($token_data);
+ }
+
+ function fetchAccessToken() {
+ $token_data = base64_decode($this->option["at"]);
+ return YahooOAuthAccessToken::from_string($token_data);
+ }
+
+ function clearRequestToken() {
+ $this->option["rt"] = NULL;
+ return update_option($this->optionName, $this->option);
+ }
+
+ function clearAccessToken() {
+ $this->option["at"] = NULL;
+ return update_option($this->optionName, $this->option);
+ }
+ }
+?>
76 yupdates_utils.php
@@ -0,0 +1,76 @@
+<?php
+ function yupdates_has_session($application, $session_store) {
+ if($session_store->hasAccessToken())
+ {
+ $access_token = $session_store->fetchAccessToken();
+ // $application->token = $access_token;
+ $access_token = $application->getAccessToken($access_token);
+ $session_store->storeAccessToken($access_token);
+
+ return ($application->token && $application->token->key);
+ }
+ else if($session_store->hasRequestToken())
+ {
+ $request_token = $session_store->fetchRequestToken();
+
+ if(array_key_exists("oauth_token", $_REQUEST) && array_key_exists("oauth_verifier", $_REQUEST)) {
+ $oauth_verifier = $_REQUEST["oauth_verifier"];
+ $access_token = $application->getAccessToken($request_token, $oauth_verifier);
+
+ if($access_token->key && $access_token->secret) {
+ $session_store->clearRequestToken();
+ $session_store->storeAccessToken($access_token);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+ }
+ else
+ {
+ $callback_params = array("auth_popup"=>"true");
+ $callback = yupdates_get_oauthCallback($callback_params);
+ $request_token = $application->getRequestToken($callback);
+
+ $session_store->storeRequestToken($request_token);
+
+ return FALSE;
+ }
+ }
+
+ function yupdates_clear_session() {
+ $session_store = yupdates_get_sessionStore();
+
+ $session_store->clearRequestToken();
+ $session_store->clearAccessToken();
+
+ // todo: infinite looping
+ header(sprintf("Location: %s", $_SERVER["REQUEST_URI"]));
+ exit();
+ }
+
+ function yupdates_get_oauthCallback($parameters) {
+ return sprintf("http://%s%s&%s",$_SERVER["HTTP_HOST"], $_SERVER["REQUEST_URI"], http_build_query($parameters));
+ }
+
+ function yupdates_get_sessionStore() {
+ if(!$yupdates_session_store) {
+ global $current_user;
+ get_currentuserinfo();
+
+ $user = $current_user->user_login;
+ $ck = get_option("yupdates_consumer_key");
+
+ $yupdates_session_store = new WordPressSessionStore($user, $ck);
+ }
+ return $yupdates_session_store;
+ }
+
+ function yupdates_close_popup() {
+?>
+<script type="text/javascript">
+ window.close();
+</script>
+<?php
+ }
+?>
108 yupdates_widgets.php
@@ -0,0 +1,108 @@
+<?php
+
+function yupdates_yahoo_updates_widget($args) {
+ extract($args);
+
+ $ck = get_option("yupdates_consumer_key");
+ $cks = get_option("yupdates_consumer_secret");
+ $appid = get_option("yupdates_application_id");
+
+ $users = yupdatesdb_listUpdatesUsers();
+ $updates = array();
+ foreach($users as $user) {
+ YahooSession::setSessionStore(new WordPressSessionStore($user));
+ if(YahooSession::hasSession($ck, $cks, $appid)) {
+ $session = YahooSession::requireSession($ck, $cks, $appid);
+ $user = $session->getSessionedUser();
+ $userUpdates = $user->listUpdates(0, 20);
+ if(!is_null($userUpdates)) {
+ $updates = array_merge($updates, $userUpdates);
+ }
+ }
+ }
+
+ usort($updates, "yupdates_compare_updates");
+
+ echo $before_widget;
+ echo $before_title . "Yahoo! Updates" . $after_title;
+ $includedUpdates = 0;
+ $widgetCount = yupdatesdb_getWidgetCount();
+ for($i = 0; $i < count($updates) && $includedUpdates < $widgetCount; $i++) {
+ $update = $updates[$i];
+ if(!property_exists($update, "SCL") || $update->SCL === "PUBLIC") {
+ echo sprintf("<div><div style='display: block; float: left; clear: none'><img src='%s' style='width: 16px'></div><div style='margin: 0px 0px 0px 22px'><a target='_new' href='%s'>%s</a><br>%s</div></div>",
+ $update->loc_iconURL, $update->link, $update->loc_longForm,
+ yupdates_ago($update->lastUpdated));
+ if($i + 1 < count($updates)) {
+ echo "<hr>";
+ }
+ $includedUpdates++;
+ }
+ }
+ if($includedUpdates == 0) {
+ echo "No updates at this time";
+ }
+ echo $after_widget;
+}
+
+function yupdates_yahoo_updates_widget_control() {
+ $count = yupdatesdb_getWidgetCount();
+ if($_POST["yupdateswidgetsubmit"]) {
+ $newCount = $_POST["yupdateswidgetcount"];
+ if(is_numeric($newCount) && ($newCount >= 0)) {
+ yupdatesdb_setWidgetCount($newCount);
+ $count = $newCount;
+ }
+ }
+?>
+ <p><label for="yupdates-widget-count">Number of Updates to Display: <input type="text" id="yupdates-widget-count" name="yupdateswidgetcount" size="2" value="<?php echo $count; ?>"></label></p>
+ <input type="hidden" name="yupdateswidgetsubmit" value="1">
+<?php
+}
+
+if(YUPDATES_WIDGET_ENABLED) {
+ add_action("init", "yupdates_register_widgets");
+}
+
+function yupdates_register_widgets() {
+ register_sidebar_widget("Yahoo! Updates", "yupdates_yahoo_updates_widget", null, "yupdates");
+ register_widget_control("Yahoo! Updates", "yupdates_yahoo_updates_widget_control", null, 75, "yupdates");
+}
+
+function yupdates_compare_updates($a, $b) {
+ return $b->lastUpdated - $a->lastUpdated;
+}
+
+function yupdates_ago($timestamp) {
+ $difference = time() - $timestamp;
+ $unit = NULL;
+
+ if($difference < 60) {
+ return "moments ago";
+ }
+ else {
+ $difference = round($difference / 60);
+ if($difference < 60) {
+ $unit = $difference == 1 ? "minute" : "minutes";
+ }
+ else {
+ $difference = round($difference / 60);
+ if($difference < 24) {
+ $unit = $difference == 1 ? "hour" : "hours";
+ }
+ else {
+ $difference = round($difference / 24);
+ if($difference < 7) {
+ $unit = $difference == 1 ? "day" : "days";
+ }
+ else {
+ return "a while ago";
+ }
+ }
+ }
+ }
+
+ return sprintf("%d %s ago", $difference, $unit);
+}
+
+?>

0 comments on commit 4f9b238

Please sign in to comment.
Something went wrong with that request. Please try again.