web/common.php
changeset 1558 761ba7426984
parent 1557 7c67caaafdeb
child 1571 4a1e6952afe5
--- a/web/common.php	Mon Nov 20 18:10:58 2023 +0100
+++ b/web/common.php	Tue Sep 03 11:09:40 2024 +0200
@@ -4,29 +4,32 @@
 require __DIR__ . '/vendor/autoload.php';
 
 use Abraham\TwitterOAuth\TwitterOAuth;
-use Fundevogel\Mastodon\Api;
+use GuzzleHttp\Client;
+use GuzzleHttp\Psr7\URI;
 
 /**
-* Base configuration
-*/
+ * Base configuration
+ */
 date_default_timezone_set('UTC');
 
+$MASTODON_DEFAULT_GROUP_DOMAIN = 'a.gup.pe';
+
 $project_url_base = 'ldtplatform/ldt/cljson/id/';
 
 $C_default_rep = 'enmi21';
 $C_feedback_form_url = 'https://spreadsheets.google.com/spreadsheet/viewform?hl=en_US&formkey=dDZILVdXVHRzd0xhWGVZXzkweHN2RGc6MQ#gid=0';
 $C_event_props = __DIR__ . "/event_props";
-$C_event_users = array('polemictweet' => 'polemictweet'.date('Y/m/d'));
+$C_event_users = array('polemictweet' => 'polemictweet' . date('Y/m/d'));
 
 $C_openssl_cipher_iv_length = 16; //openssl_cipher_iv_length('aes256')
 
 $archives_list = array(
     "rsln", "rsln-opendata", "rsln-mercedes-bunz",
-    "enmi2011-technologie-confiance", "CPV", array("fens_FabLab_Design_Metadata","fablab"),
-array("fens_FabLab_Design_Metadata","designmd"), array("mashup","conference"), array("mashup","tableronde"),
+    "enmi2011-technologie-confiance", "CPV", array("fens_FabLab_Design_Metadata", "fablab"),
+    array("fens_FabLab_Design_Metadata", "designmd"), array("mashup", "conference"), array("mashup", "tableronde"),
     "sig-chi-paris-2011", "rwd-meetup-patrimoine", "2011-2012-museo-ouverture"/*, "JaneMcGonigal-gameDesign"*/,
     "iii-catastrophe", "edito-inaugurale", "enmi2011", "2011-2012-museo-structured-data",
-    "edito-webdoc","edito-intelligence", "2011-2012-museo-contribution",
+    "edito-webdoc", "edito-intelligence", "2011-2012-museo-contribution",
     "2011-2012-museo-ingenierie", "edito-serious-games", "enmi2012-seminaire-1", "2011-2012-museo-audiovisuel", "edito-reseaux-sociaux",
     "edito-arts-numeriques",
     'fens2012-gamestudies',
@@ -49,37 +52,36 @@
     'attention-1314-02-syndrome-saturation-cognitive',
     'edito-1314-01-quest-ce-quun-support',
     'enmi13', 'museo-1314-03-cognition-apprentissage', 'attention-1314-03-nouvelle-valeur-economique',
-    'edito-1314-02-lannotation-le-savoir-dans-la-marge', 'museo-1314-04-lecture-active' , 'edito-1314-03-traduction', 'attention-1314-04-pathologies-attention-memoire', 'edito-1314-04-environnement-support', 'attention-1314-05-marche-attention-avenement-publicite','museo-1314-05-nouvelles-editions', 'edito-1314-05-algorithme',
-    'fens2014-design-metadata','fens2014-museo','fens2014-transmediamix','fens2014-edito','fens2014-attention',
-    'pour-la-transition-une-conomie-du-partage-de-la-connaissance-et-des-biens-communs','spel-01-gout-archivage', 'edito-1415-01-hybridation-pratiques-recherche',
+    'edito-1314-02-lannotation-le-savoir-dans-la-marge', 'museo-1314-04-lecture-active', 'edito-1314-03-traduction', 'attention-1314-04-pathologies-attention-memoire', 'edito-1314-04-environnement-support', 'attention-1314-05-marche-attention-avenement-publicite', 'museo-1314-05-nouvelles-editions', 'edito-1314-05-algorithme',
+    'fens2014-design-metadata', 'fens2014-museo', 'fens2014-transmediamix', 'fens2014-edito', 'fens2014-attention',
+    'pour-la-transition-une-conomie-du-partage-de-la-connaissance-et-des-biens-communs', 'spel-01-gout-archivage', 'edito-1415-01-hybridation-pratiques-recherche',
     'museo-1415-01-inaugurale', 'museo-1415-02-controverses', 'attention-1415-01-attention-automatisee',
-    'museo-1415-03-pedagogies', 'museo-1415-04-reseaux-sociaux-hermeneutiques','edito-1415-02-ressources-documentation-recherche', 'enmi14',
+    'museo-1415-03-pedagogies', 'museo-1415-04-reseaux-sociaux-hermeneutiques', 'edito-1415-02-ressources-documentation-recherche', 'enmi14',
     'attention-1415-02-recherche-algo-attention-hermeneutique', 'edito-1415-04-elargissement-communautes-scientifiques', /*'museo-1415-05-interfaces-design',*/
     'museo-1415-06-multilinguisme', 'museo-1415-07-traduction', 'attention-1415-05-reseaux-sociaux-valorisation',
     'attention-1415-06-game-design', 'edito-1415-05-faire-oeuvre-epoque-numerique', 'museo-1415-08-histoire-critique',
     'attention-1415-07-design-pluralisation', 'edito-1516-01-profil-collectif', 'edito-1516-02-corps-profil', 'enmi15', 'edito-1516-03-editorialisation-universitaire',
     'edito-1516-04-detournements-creation', 'edito-1516-05-production-reel', 'edito-1516-06-confession-confiscation-de-soi',
     'edito-1516-07-architecture-savoir', 'edito-1516-08-desir-profilage', 'edito-1516-09-cloture', 'journee-omnsh-2016-10-patrimoine-numerique', 'enmi16',
-    'cnsad-ateliers-2017', 'crypto-party-camp-rouxteur-10-2017', 'cnsad-seminaire-telepresence-11-2017', 'enmi17', /*'enmi18-preparatoire',*/'marathon-serpentine-2018', 'enmi18', 'enmi19-preparatoire', 'enmi19',
+    'cnsad-ateliers-2017', 'crypto-party-camp-rouxteur-10-2017', 'cnsad-seminaire-telepresence-11-2017', 'enmi17', /*'enmi18-preparatoire',*/ 'marathon-serpentine-2018', 'enmi18', 'enmi19-preparatoire', 'enmi19',
     'enmi20', 'enmi21',
 );
 
-
 $req_rep = $C_default_rep;
-if(isset($config) && isset($config['rep'])) {
+if (isset($config) && isset($config['rep'])) {
     $req_rep = $config['rep'];
 }
 
-foreach (glob(dirname(__FILE__).'/traductions/*.php') as $trad_filename)
-{
+foreach (glob(dirname(__FILE__) . '/traductions/*.php') as $trad_filename) {
     include_once $trad_filename;
 }
-if(file_exists(dirname(__FILE__)."/$req_rep/traduction.php")) {
-    include_once dirname(__FILE__)."/$req_rep/traduction.php";
+if (file_exists(dirname(__FILE__) . "/$req_rep/traduction.php")) {
+    include_once dirname(__FILE__) . "/$req_rep/traduction.php";
 }
 
-$appCacheHandle = new SQLite3(dirname(__FILE__)."/data/app_cache.db");
-$appCacheHandle->query("CREATE TABLE IF NOT EXISTS apps (id INTEGER PRIMARY KEY, domain TEXT UNIQUE NOT NULL, app_key TEXT UNIQUE NOT NULL, app_secret TEXT NOT NULL)");
+$appCacheHandle = new SQLite3(dirname(__FILE__) . "/data/app_cache.db");
+$appCacheHandle->query("CREATE TABLE IF NOT EXISTS apps (id INTEGER PRIMARY KEY, domain TEXT NOT NULL, event TEXT NOT NULL, client_id TEXT UNIQUE NOT NULL, client_secret TEXT NOT NULL, UNIQUE(domain, event))");
+
 
 
 
@@ -91,53 +93,121 @@
 /**
  * Include the configuration data for our OAuth Client (array $configuration)
  */
-include_once dirname(__FILE__).'/config.php';
+include_once dirname(__FILE__) . '/config.php';
+
+function get_cached_app_ids($login_domain, $req_rep, $appCacheHandle) {
+
+    $statement = $appCacheHandle->prepare('SELECT * FROM apps WHERE domain = :domain and event = :event;');
+    $statement->bindValue(':domain', $login_domain);
+    $statement->bindValue(':event', $req_rep);
 
+    $result = $statement->execute();
+    $client_id = false;
+    $client_secret = false;
+    if ($row = $result->fetchArray()) {
+        $client_id = $row["client_id"];
+        $client_secret = $row["client_secret"];
+    }
 
-$get_social_request_token = function ($loginDomain, $config) use ($req_rep, $appCacheHandle) {
+    return [ "client_id" => $client_id, "client_secret" => $client_secret];
+}
+
+$get_social_request_token = function ($login_domain, $config) use ($req_rep, $appCacheHandle) {
 
     $socialNetwork = $config['social_network'];
 
-    if($socialNetwork == "Twitter") {
+    if ($socialNetwork == "Twitter") {
 
         $twitterClient = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
-        $token = $twitterClient->oauth('oauth/request_token', array('oauth_callback' => URL_ROOT."callback.php?rep=$req_rep"));
+        $token = $twitterClient->oauth('oauth/request_token', array('oauth_callback' => URL_ROOT . "callback.php?rep=$req_rep"));
 
         $_SESSION['TWITTER_REQUEST_TOKEN'] = serialize($token);
 
         /**
-        * Now redirect user to Twitter site so they can log in and
-        * approve our access
-        */
+         * Now redirect user to Twitter site so they can log in and
+         * approve our access
+         */
 
         $url = $twitterClient->url('oauth/authorize', array('oauth_token' => $token['oauth_token']));
-        header("Location: ".$url);
-        die();
-    } else if($socialNetwork == "Mastodon") {
-        $mastodonApi = new Api($loginDomain);
+    } else if ($socialNetwork == "Mastodon") {
+
+        $_SESSION['SOCIAL_LOGIN_DOMAIN'] = $login_domain;
+
+        $base_uri = "https://$login_domain";
+        $client = new Client([
+            'base_uri' => $base_uri,
+            'timeout'  => 2.0,
+        ]);
+
+        $client_ids = get_cached_app_ids($login_domain, $req_rep, $appCacheHandle);
+        $client_id = $client_ids["client_id"];
+        $client_secret = $client_ids["client_secret"];
+
+        if(!$client_id || !$client_secret) {
+            // Create app
+            $resp = $client->post('/api/v1/apps', ['form_params' => [
+                "client_name" => "PolemicToot-$req_rep",
+                "redirect_uris" => URL_ROOT . "callback.php?rep=$req_rep",
+                "scopes" => 'read write push'
+            ]]);
+            $appsDef = json_decode($resp->getBody());
+            //var_dump($appsDef);
+            $client_id = $appsDef->client_id;
+            $client_secret = $appsDef->client_secret;
+            $appCacheHandle->exec("INSERT INTO apps (domain, event, client_id, client_secret) VALUES ('$login_domain', '$req_rep', '$client_id', '$client_secret')");
+        }
+
+        $state = bin2hex(random_bytes(12));
+
+        $_SESSION['SOCIAL_AUTH_STATE'] = $state;
 
-        $cachedApp = $appCacheHandle->querySingle("");
-        $statement = $cachedApp->prepare('SELECT * FROM apps WHERE domain = :domain;');
-        $statement->bindValue(':domain', $loginDomain);
+        $uri = URI::withQueryValues(URI::fromParts([
+            'scheme' => 'https',
+            'host' => $login_domain,
+            'path' => '/oauth/authorize'
+        ]), [
+            "response_type" => "code",
+            "client_id" => "$client_id",
+            "redirect_uri" => URL_ROOT . "callback.php?rep=$req_rep",
+            "scope" => 'read write push',
+            "state" => $state
+        ]);
+
+        $url = (string)$uri;
+    }
+    header("Location: " . $url);
+    die();
+};
 
-$result = $statement->execute();
-            // Create app
-        $appsDef = $mastodonApi->apps()->create("PolemicToot", URL_ROOT."callback.php?rep=$req_rep", 'read write push');
-        var_dump($appsDef);
-        //$mastodonApi->logIn();
+function setGroupMastodon($config, $default_domain) {
+    if(strtolower(($config['social_network'] ?? 'Twitter')) === "twitter" ) {
+        return $config;
+    }
+    $group = '@'. ltrim($config['group'] ?? $config['hashtag'], '@');
+
+    if(!preg_match("/.+@.+$/", $group)) {
+        $group = $group . "@" . $default_domain;
     }
-};
+
+    $res = $config;
+    $res['group'] = $group;
+
+    return $res;
+}
+
+
 
 /**
  * TRADUCTION
-**/
-function get_config_translations($config) {
+ **/
+function get_config_translations($config)
+{
 
     $fr = array();
     $en = array();
     $jp = array();
 
-    $array_loop = array("fr"=>&$fr,"en"=>&$en,"jp"=>&$jp);
+    $array_loop = array("fr" => &$fr, "en" => &$en, "jp" => &$jp);
     /**
      * add all config key as translation.
      * translation key is "config__<config_key>"
@@ -146,25 +216,23 @@
         $translation_key = "config__$key";
 
         foreach ($array_loop as $lang => &$lang_array) {
-            if(is_array($value) && count(array_intersect_key($value,$array_loop)) > 0 ) {
+            if (is_array($value) && count(array_intersect_key($value, $array_loop)) > 0) {
                 if (array_key_exists($lang, $value)) {
                     $lang_array[$translation_key] = $value[$lang];
-                }
-                elseif (array_key_exists('fr', $value)) {
+                } elseif (array_key_exists('fr', $value)) {
                     $lang_array[$translation_key] = $value['fr'];
                 }
-            }
-            else {
+            } else {
                 $lang_array[$translation_key] = $value;
             }
         }
     }
 
     return $array_loop;
-
 }
 
-function set_config_translations(&$config, &$translate) {
+function set_config_translations(&$config, &$translate)
+{
 
     $config_translations = get_config_translations($config);
 
@@ -189,162 +257,159 @@
 $translate->addTranslation($traduction_ja, 'ja_JP');
 $translate->addTranslation($traduction_fr, 'fr');
 
-if(isset($config)) {
+if (isset($config)) {
     set_config_translations($config, $translate);
+    $config = setGroupMastodon($config, $MASTODON_DEFAULT_GROUP_DOMAIN);
 }
 
 $actual = $translate->getLocale();
 
 
-if(isset($_GET['lang'])==false and isset($_SESSION['lang'])==false){
+if (isset($_GET['lang']) == false and isset($_SESSION['lang']) == false) {
 
-    if($actual!='fr' and $actual!='en' and $actual!='ja_JP'){
+    if ($actual != 'fr' and $actual != 'en' and $actual != 'ja_JP') {
         $translate->setLocale("fr");
-         $_SESSION['lang']="fr";
+        $_SESSION['lang'] = "fr";
     }
-
-} else if (isset($_GET['lang'])==true){
+} else if (isset($_GET['lang']) == true) {
     $translate->setLocale($_GET['lang']);
     $_SESSION['lang'] = $_GET['lang'];
     $actual = $_SESSION['lang'];
-
-} else if (isset($_SESSION['lang'])==true){
-    $translate->setLocale( $_SESSION['lang']);
+} else if (isset($_SESSION['lang']) == true) {
+    $translate->setLocale($_SESSION['lang']);
     $actual = $_SESSION['lang'];
-
 }
 
 $js_registry = array(
     'local' => array(
-        'libdir'        => URL_ROOT.'res/js/',
-        'jquery' 		=> URL_ROOT.'res/js/jquery-1.10.2.min.js',
-        'raphael' 		=> URL_ROOT.'res/js/raphael-min.js',
-        'jquery-ui' 	=> URL_ROOT.'res/js/jquery-ui.min.js',
-        'niceforms' 	=> URL_ROOT.'res/js/niceforms.js',
-        'jquery-url' 	=> URL_ROOT.'res/js/jquery.url.js',
-        'ldtplayer' 	=> URL_ROOT.'res/metadataplayer/src/js/LdtPlayer.js',
-        'fancybox' 		=> URL_ROOT.'res/js/fancybox/jquery.fancybox.pack.js',
-        'jquery-tools' 	=> URL_ROOT.'res/js/jquery.tools.min.js',
-        'jquery-migrate' => URL_ROOT.'res/js/jquery-migrate-1.4.1.min.js',
-        'tw-widget' 	=> URL_ROOT.'res/js/tw_widget.js',
-        'jquery-mousewheel' => URL_ROOT.'res/js/jquery.mousewheel.js',
-        'swfobject' 	=> URL_ROOT.'res/js/swfobject.js',
-        'json-js' 		=> URL_ROOT.'res/js/json2.js',
-        'underscore'    => URL_ROOT.'res/js/underscore-min.js' ,
-        'jquery-scrollto'=>URL_ROOT.'res/js/jquery.scrollTo-2.1.2-min.js' ,
-        'twcx-main'     => URL_ROOT.'res/js/live-polemic.js' ,
-        'semanticboard' => URL_ROOT.'res/js/semanticboard.js' ,
-        'metadataplayer'=> URL_ROOT.'res/metadataplayer/LdtPlayer-core.js' ,
-        'ldtwidgets'    => URL_ROOT.'res/metadataplayer/' ,
-        'tracemanager'  => URL_ROOT.'res/js/tracemanager.js' ,
-        'jwplayer-js'   => URL_ROOT.'res/js/jwplayer.js',
-        'jquery-tinymce' => URL_ROOT.'res/js/tinymce/jquery.tinymce.min.js',
-        'tinymce'        => URL_ROOT.'res/js/tinymce/tinymce.min.js',
-        'dashjs'         => URL_ROOT.'res/js/dashjs/dash.min.js',
-        'videojs-dash'   => URL_ROOT.'res/js/dashjs/videojs-dash.min.js',
-        'videojs'        => URL_ROOT.'res/js/videojs/video.min.js',
-        'twitter-text'   => URL_ROOT.'res/js/twitter-text-3.0.1.min.js',
+        'libdir'        => URL_ROOT . 'res/js/',
+        'jquery'         => URL_ROOT . 'res/js/jquery.min.js',
+        'raphael'         => URL_ROOT . 'res/js/raphael-min.js',
+        'jquery-ui'     => URL_ROOT . 'res/js/jquery-ui.min.js',
+        'niceforms'     => URL_ROOT . 'res/js/niceforms.js',
+        'jquery-url'     => URL_ROOT . 'res/js/jquery.url.js',
+        'ldtplayer'     => URL_ROOT . 'res/metadataplayer/src/js/LdtPlayer.js',
+        'fancybox'         => URL_ROOT . 'res/js/fancybox/jquery.fancybox.pack.js',
+        'jquery-tools'     => URL_ROOT . 'res/js/jquery.tools.min.js',
+        'jquery-migrate' => URL_ROOT . 'res/js/jquery-migrate-1.4.1.min.js',
+        'tw-widget'     => URL_ROOT . 'res/js/tw_widget.js',
+        'jquery-mousewheel' => URL_ROOT . 'res/js/jquery.mousewheel.js',
+        'swfobject'     => URL_ROOT . 'res/js/swfobject.js',
+        'json-js'         => URL_ROOT . 'res/js/json2.js',
+        'underscore'    => URL_ROOT . 'res/js/underscore-min.js',
+        'jquery-scrollto' => URL_ROOT . 'res/js/jquery.scrollTo-2.1.2-min.js',
+        'twcx-main'     => URL_ROOT . 'res/js/live-polemic.js',
+        'semanticboard' => URL_ROOT . 'res/js/semanticboard.js',
+        'metadataplayer' => URL_ROOT . 'res/metadataplayer/LdtPlayer-core.js',
+        'ldtwidgets'    => URL_ROOT . 'res/metadataplayer/',
+        'tracemanager'  => URL_ROOT . 'res/js/tracemanager.js',
+        'jwplayer-js'   => URL_ROOT . 'res/js/jwplayer.js',
+        'jquery-tinymce' => URL_ROOT . 'res/js/tinymce/jquery.tinymce.min.js',
+        'tinymce'        => URL_ROOT . 'res/js/tinymce/tinymce.min.js',
+        'dashjs'         => URL_ROOT . 'res/js/dashjs/dash.min.js',
+        'videojs-dash'   => URL_ROOT . 'res/js/dashjs/videojs-dash.min.js',
+        'videojs'        => URL_ROOT . 'res/js/videojs/video.min.js',
+        'twitter-text'   => URL_ROOT . 'res/js/twitter-text-3.0.1.min.js',
     ),
     'cdn' => array(
-        'libdir'        => URL_ROOT.'res/js/',
-        'jquery' 		=> 'http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js',
-        'raphael' 		=> URL_ROOT.'res/js/raphael-min.js',
-        'jquery-ui' 	=> 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js',
-        'niceforms' 	=> URL_ROOT.'res/js/niceforms.js',
-        'jquery-url' 	=> URL_ROOT.'res/js/jquery.url.js',
-        'ldtplayer' 	=> URL_ROOT.'res/metadataplayer/src/js/LdtPlayer.js',
-        'fancybox' 		=> URL_ROOT.'res/js/fancybox/jquery.fancybox.pack.js',
-        'jquery-tools' 	=> 'http://cdn.jquerytools.org/1.2.7/all/jquery.tools.min.js',
+        'libdir'        => URL_ROOT . 'res/js/',
+        'jquery'         => 'http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js',
+        'raphael'         => URL_ROOT . 'res/js/raphael-min.js',
+        'jquery-ui'     => 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js',
+        'niceforms'     => URL_ROOT . 'res/js/niceforms.js',
+        'jquery-url'     => URL_ROOT . 'res/js/jquery.url.js',
+        'ldtplayer'     => URL_ROOT . 'res/metadataplayer/src/js/LdtPlayer.js',
+        'fancybox'         => URL_ROOT . 'res/js/fancybox/jquery.fancybox.pack.js',
+        'jquery-tools'     => 'http://cdn.jquerytools.org/1.2.7/all/jquery.tools.min.js',
         'jquery-migrate' => 'http://code.jquery.com/jquery-migrate-1.4.1.min.js',
-        'tw-widget' 	=> 'http://widgets.twimg.com/j/2/widget.js',
-        'jquery-mousewheel' => URL_ROOT.'res/js/jquery.mousewheel.js',
-        'swfobject' 	=> 'http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js',
-        'json-js'       => URL_ROOT.'res/js/json2.js',
-        'underscore'    => URL_ROOT.'res/js/underscore-min.js' ,
-        'jquery-scrollto'=>URL_ROOT.'res/js/jquery.scrollTo-2.1.2-min.js' ,
-        'twcx-main'     => URL_ROOT.'res/js/live-polemic.js' ,
-        'semanticboard' => URL_ROOT.'res/js/semanticboard.js' ,
-        'metadataplayer'=> URL_ROOT.'res/metadataplayer/LdtPlayer-core.js' ,
-        'ldtwidgets'    => URL_ROOT.'res/metadataplayer/' ,
-        'tracemanager'  => URL_ROOT.'res/js/tracemanager.js' ,
-        'jwplayer-js'   => URL_ROOT.'res/js/jwplayer.js',
-        'jquery-tinymce' => URL_ROOT.'res/js/tinymce/jquery.tinymce.min.js',
-        'tinymce'        => URL_ROOT.'res/js/tinymce/tinymce.min.js',
+        'tw-widget'     => 'http://widgets.twimg.com/j/2/widget.js',
+        'jquery-mousewheel' => URL_ROOT . 'res/js/jquery.mousewheel.js',
+        'swfobject'     => 'http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js',
+        'json-js'       => URL_ROOT . 'res/js/json2.js',
+        'underscore'    => URL_ROOT . 'res/js/underscore-min.js',
+        'jquery-scrollto' => URL_ROOT . 'res/js/jquery.scrollTo-2.1.2-min.js',
+        'twcx-main'     => URL_ROOT . 'res/js/live-polemic.js',
+        'semanticboard' => URL_ROOT . 'res/js/semanticboard.js',
+        'metadataplayer' => URL_ROOT . 'res/metadataplayer/LdtPlayer-core.js',
+        'ldtwidgets'    => URL_ROOT . 'res/metadataplayer/',
+        'tracemanager'  => URL_ROOT . 'res/js/tracemanager.js',
+        'jwplayer-js'   => URL_ROOT . 'res/js/jwplayer.js',
+        'jquery-tinymce' => URL_ROOT . 'res/js/tinymce/jquery.tinymce.min.js',
+        'tinymce'        => URL_ROOT . 'res/js/tinymce/tinymce.min.js',
         'dashjs'         => "http://cdn.dashjs.org/latest/dash.all.min.js",
         'videojs-dash'   => 'https://cdnjs.cloudflare.com/ajax/libs/videojs-contrib-dash/2.5.2/videojs-dash.min.js',
         'videojs'        => 'http://vjs.zencdn.net/5.12.6/video.js',
-        'twitter-text'   => URL_ROOT.'res/js/twitter-text-3.0.1.min.js',
+        'twitter-text'   => URL_ROOT . 'res/js/twitter-text-3.0.1.min.js',
     )
 );
 
 $font_registry = array(
     'local' => array(
-        'PT-Sans_Narrow' => URL_ROOT.'res/fonts/PTSansNarrow.css',
-        'PT-Sans' 		 => URL_ROOT.'res/fonts/PTSans.css'
+        'PT-Sans_Narrow' => URL_ROOT . 'res/fonts/PTSansNarrow.css',
+        'PT-Sans'          => URL_ROOT . 'res/fonts/PTSans.css'
     ),
     'cdn' => array(
         'PT-Sans_Narrow' => 'http://fonts.googleapis.com/css?family=PT+Sans+Narrow&subset=latin',
-        'PT-Sans' 		 => 'http://fonts.googleapis.com/css?family=PT+Sans&subset=latin'
+        'PT-Sans'          => 'http://fonts.googleapis.com/css?family=PT+Sans&subset=latin'
     )
 );
 
 
 $css_registry = array(
     'local' => array(
-       	'blueprint-screen' => URL_ROOT.'res/css/blueprint-screen.css',
-        'blueprint-print' => URL_ROOT.'res/css/blueprint-print.css',
-        'blueprint-ie' => URL_ROOT.'res/css/blueprint-ie.css',
-        'blueprint-plugins-fancy-type' => URL_ROOT.'res/css/blueprint-plugins/fancy-type/screen.css',
-        'custom' => URL_ROOT.'res/css/custom.css',
-        'fancybox' => URL_ROOT.'res/js/fancybox/jquery.fancybox.css',
-        'jquery-ui' => URL_ROOT.'res/metadataplayer/res/css/jq-css/themes/base/jquery-ui.css',
-        'tabs-slideshow' => URL_ROOT.'res/css/tabs-slideshow.css',
-        'tweetcast' => URL_ROOT.'res/css/tweetcast.css',
-        'semanticboard' =>  URL_ROOT.'res/css/semanticboard.css',
-        'archives-iframe' => URL_ROOT.'res/css/archives-iframe.css',
-        'metadataplayer' => URL_ROOT.'res/metadataplayer/LdtPlayer-core.css',
-        'jquery-te' => URL_ROOT.'res/css/jquery-te.css',
-        'videojs' => URL_ROOT.'res/js/videojs/video-js.min.css',
+        'blueprint-screen' => URL_ROOT . 'res/css/blueprint-screen.css',
+        'blueprint-print' => URL_ROOT . 'res/css/blueprint-print.css',
+        'blueprint-ie' => URL_ROOT . 'res/css/blueprint-ie.css',
+        'blueprint-plugins-fancy-type' => URL_ROOT . 'res/css/blueprint-plugins/fancy-type/screen.css',
+        'custom' => URL_ROOT . 'res/css/custom.css',
+        'fancybox' => URL_ROOT . 'res/js/fancybox/jquery.fancybox.css',
+        'jquery-ui' => URL_ROOT . 'res/metadataplayer/res/css/jq-css/themes/base/jquery-ui.css',
+        'tabs-slideshow' => URL_ROOT . 'res/css/tabs-slideshow.css',
+        'tweetcast' => URL_ROOT . 'res/css/tweetcast.css',
+        'semanticboard' =>  URL_ROOT . 'res/css/semanticboard.css',
+        'archives-iframe' => URL_ROOT . 'res/css/archives-iframe.css',
+        'metadataplayer' => URL_ROOT . 'res/metadataplayer/LdtPlayer-core.css',
+        'jquery-te' => URL_ROOT . 'res/css/jquery-te.css',
+        'videojs' => URL_ROOT . 'res/js/videojs/video-js.min.css',
+        'ldtplayer'     => URL_ROOT . 'res/metadataplayer/src/js/LdtPlayer.css',
     ),
     'cdn' => array(
-        'blueprint-screen' => URL_ROOT.'res/css/blueprint-screen.css',
-        'blueprint-print' => URL_ROOT.'res/css/blueprint-print.css',
-        'blueprint-ie' => URL_ROOT.'res/css/blueprint-ie.css',
-        'blueprint-plugins-fancy-type' => URL_ROOT.'res/css/blueprint-plugins/fancy-type/screen.css',
-        'custom' => URL_ROOT.'res/css/custom.css',
-        'fancybox' => URL_ROOT.'res/js/fancybox/jquery.fancybox.css',
+        'blueprint-screen' => URL_ROOT . 'res/css/blueprint-screen.css',
+        'blueprint-print' => URL_ROOT . 'res/css/blueprint-print.css',
+        'blueprint-ie' => URL_ROOT . 'res/css/blueprint-ie.css',
+        'blueprint-plugins-fancy-type' => URL_ROOT . 'res/css/blueprint-plugins/fancy-type/screen.css',
+        'custom' => URL_ROOT . 'res/css/custom.css',
+        'fancybox' => URL_ROOT . 'res/js/fancybox/jquery.fancybox.css',
         'jquery-ui' => "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/themes/base/jquery-ui.css",
-        'tabs-slideshow' => URL_ROOT.'res/css/tabs-slideshow.css',
-        'tweetcast' => URL_ROOT.'res/css/tweetcast.css',
-        'semanticboard' =>  URL_ROOT.'res/css/semanticboard.css',
-        'archives-iframe' => URL_ROOT.'res/css/archives-iframe.css',
-        'metadataplayer' => URL_ROOT.'res/metadataplayer/LdtPlayer-core.css',
-        'jquery-te' => URL_ROOT.'res/css/jquery-te.css',
+        'tabs-slideshow' => URL_ROOT . 'res/css/tabs-slideshow.css',
+        'tweetcast' => URL_ROOT . 'res/css/tweetcast.css',
+        'semanticboard' =>  URL_ROOT . 'res/css/semanticboard.css',
+        'archives-iframe' => URL_ROOT . 'res/css/archives-iframe.css',
+        'metadataplayer' => URL_ROOT . 'res/metadataplayer/LdtPlayer-core.css',
+        'jquery-te' => URL_ROOT . 'res/css/jquery-te.css',
         'videojs' => "http://vjs.zencdn.net/5.12.6/video-js.css"
     )
 );
 
 
-function registry_url($key, $type, $registry_def=null) {
+function registry_url($key, $type, $registry_def = null)
+{
 
     global $js_registry, $font_registry, $css_registry, $C_default_registry;
 
-    if($registry_def != null) {
+    if ($registry_def != null) {
         $registry = $registry_def;
-    }
-    elseif(isset($C_default_registry)) {
+    } elseif (isset($C_default_registry)) {
         $registry = $C_default_registry;
-    }
-    else {
+    } else {
         $registry = 'local';
     }
-    $registry_name = $type."_registry";
-    return ${
-        $registry_name}[$registry][$key];
-
+    $registry_name = $type . "_registry";
+    return ${$registry_name}[$registry][$key];
 }
 
-function get_default_annotations_config($config, $translate) {
+function get_default_annotations_config($config, $translate)
+{
 
     $default_protocol_annotations = array(
         "1" => array(
@@ -387,7 +452,7 @@
                 ),
                 "colors_class" => "twbYellow",
                 "polemic_cat" => 'REF',
-                "polemic_keywords" => array("==","http://"),
+                "polemic_keywords" => array("==", "http://"),
                 "polemic_color" => "#C5A62D"
             ),
             "question" => array(
@@ -399,7 +464,7 @@
                 ),
                 "colors_class" => "twbBlue",
                 "polemic_cat" => 'Q',
-                "polemic_keywords" => array("?","??"),
+                "polemic_keywords" => array("?", "??"),
                 "polemic_color" => "#036AAE"
             )
         ),
@@ -443,7 +508,7 @@
                 ),
                 "colors_class" => "twbYellow",
                 "polemic_cat" => 'REF',
-                "polemic_keywords" => array("==","http://"),
+                "polemic_keywords" => array("==", "http://"),
                 "polemic_color" => "#C5A62D"
             ),
             "question" => array(
@@ -455,7 +520,7 @@
                 ),
                 "colors_class" => "twbBlue",
                 "polemic_cat" => 'Q',
-                "polemic_keywords" => array("?","??"),
+                "polemic_keywords" => array("?", "??"),
                 "polemic_color" => "#036AAE"
             )
         ),
@@ -478,7 +543,7 @@
                 "polemic_color" => "#196be6"
             ),
             "trouble" => array( // orange
-                "display_name" => "?? | Trouble", 
+                "display_name" => "?? | Trouble",
                 "keywords" => "\\?\\?",
                 "colors" => array(
                     "h" => .13,
@@ -496,7 +561,7 @@
                     "s" => .8
                 ),
                 "polemic_cat" => 'REF',
-                "polemic_keywords" => array("**","http://"),
+                "polemic_keywords" => array("**", "http://"),
                 "polemic_color" => "#e619e6"
             ),
             "comments" => array( // green
@@ -513,16 +578,16 @@
         )
     );
 
-    $annotation_protocol_version = isset($config['annotation_protocol_version'])?$config['annotation_protocol_version']:"1";
+    $annotation_protocol_version = isset($config['annotation_protocol_version']) ? $config['annotation_protocol_version'] : "1";
 
-    $annotations_def = (isset($config['annotations']) && !empty($config['annotations']))?$config['annotations']:$default_protocol_annotations[$annotation_protocol_version];
+    $annotations_def = (isset($config['annotations']) && !empty($config['annotations'])) ? $config['annotations'] : $default_protocol_annotations[$annotation_protocol_version];
 
     $annotations = array();
 
     foreach ($annotations_def as $annot_cat => $annot_def) {
-        if(isset($annot_def['display_name'])) {
-            $disp_parts = array_map('trim',explode("|",$annot_def['display_name']));
-            if(count($disp_parts) > 1) {
+        if (isset($annot_def['display_name'])) {
+            $disp_parts = array_map('trim', explode("|", $annot_def['display_name']));
+            if (count($disp_parts) > 1) {
                 $disp_parts[1] = $translate->_($disp_parts[1]);
             }
             $annot_def['display_name'] = implode(" | ", $disp_parts);
@@ -533,24 +598,25 @@
 }
 
 
-function get_archive_box($rep, $metadata, $url_root, $basepath, &$translate) {
+function get_archive_box($rep, $metadata, $url_root, $basepath, &$translate)
+{
 
     include("$basepath$rep/config.php");
 
     set_config_translations($config, $translate);
 
-    $id = "abox_$rep".(($metadata!=null)?"_$metadata":"");
-    $hash = ($metadata!=null)?"#metadata=$metadata":"";
+    $id = "abox_$rep" . (($metadata != null) ? "_$metadata" : "");
+    $hash = ($metadata != null) ? "#metadata=$metadata" : "";
     $tail_img = $translate->_('config__archive_img');
-    if(is_array($tail_img)) {
+    if (is_array($tail_img)) {
         $tail_img = $tail_img[$metadata];
     }
     $archive_title = $translate->_('config__archive_title');
-    if(is_array($archive_title)) {
+    if (is_array($archive_title)) {
         $archive_title = $archive_title[$metadata];
     }
     $archive_description = $translate->_('config__archive_description');
-    if(is_array($archive_description)) {
+    if (is_array($archive_description)) {
         $archive_description = $archive_description[$metadata];
     }
 
@@ -566,41 +632,43 @@
     $res .= "</div>\n";
 
     return $res;
-
 }
 
-function display_archives_list($archives_list, $box_class, $url_root, $basepath, &$translate) {
+function display_archives_list($archives_list, $box_class, $url_root, $basepath, &$translate)
+{
 
 
-    for($i=0;$i<count($archives_list);$i++) {
-        if(($i % 3)==0) {
+    for ($i = 0; $i < count($archives_list); $i++) {
+        if (($i % 3) == 0) {
             print("			    <div class=\"$box_class\">\n");
         }
         $archive_ref = $archives_list[$i];
 
         $archive_name = $archive_ref;
         $metadata = null;
-        if(is_array($archive_ref)) {
+        if (is_array($archive_ref)) {
             $archive_name = $archive_ref[0];
             $metadata = $archive_ref[1];
         }
-        print(get_archive_box($archive_name,$metadata, $url_root, $basepath, $translate));
-        if(($i % 3)==2 || $i == (count($archives_list)-1)) {
+        print(get_archive_box($archive_name, $metadata, $url_root, $basepath, $translate));
+        if (($i % 3) == 2 || $i == (count($archives_list) - 1)) {
             print("			    </div>\n");
         }
     }
 }
 
-function get_metadata_url($metadata) {
+function get_metadata_url($metadata)
+{
     global $project_url_base;
     if (preg_match('/^https?:\/\//', $metadata)) {
         return $metadata;
     } else {
-        return LDT_PLATFORM.$project_url_base.$metadata;
+        return LDT_PLATFORM . $project_url_base . $metadata;
     }
 }
 
-function get_metadata_json_url($metadata) {
+function get_metadata_json_url($metadata)
+{
     if (is_array($metadata)) {
         $metadata["url"] = get_metadata_url($metadata["url"]);
         return $metadata;
@@ -613,25 +681,28 @@
 $realm = 'Polemictweet restricted area';
 
 
-function authenticate($users, $translate) {
+function authenticate($users, $translate)
+{
 
     global $realm;
 
     $_SESSION['auth'] = 'polemictweet';
-    return array('username'=>'polemictweet');
+    return array('username' => 'polemictweet');
 
     if (empty($_SERVER['PHP_AUTH_DIGEST']) || !isset($_SESSION['http_digest_nonce'])) {
         $_SESSION['http_digest_nonce'] = uniqid();
         header('HTTP/1.1 401 Unauthorized');
-        header('WWW-Authenticate: Digest realm="'.$realm.
-        '",qop="auth",nonce="'.$_SESSION['http_digest_nonce'].'",opaque="'.md5($realm).'"');
+        header('WWW-Authenticate: Digest realm="' . $realm .
+            '",qop="auth",nonce="' . $_SESSION['http_digest_nonce'] . '",opaque="' . md5($realm) . '"');
         return array('error' => $translate->_('This area is restricted, please authenticate'));
     }
 
 
     //analyze the PHP_AUTH_DIGEST variable
-    if (!($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
-        !isset($users[$data['username']])) {
+    if (
+        !($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) ||
+        !isset($users[$data['username']])
+    ) {
         $_SERVER['PHP_AUTH_DIGEST'] = '';
         unset($_SESSION['auth']);
         unset($_SESSION['http_digest_nonce']);
@@ -641,8 +712,8 @@
 
     //generate the valid response
     $A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
-    $A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
-    $valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
+    $A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']);
+    $valid_response = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . $A2);
 
     if ($data['response'] != $valid_response) {
         $_SERVER['PHP_AUTH_DIGEST'] = '';
@@ -656,11 +727,11 @@
     return $data;
 }
 
-function logout() {
-    global $realm;
+function logout()
+{
+    global $_SESSION;
 
-    unset($_SESSION['auth']);
-    unset($_SESSION['http_digest_nonce']);
+    $_SESSION = [];
 }
 
 
@@ -668,7 +739,7 @@
 function http_digest_parse($txt)
 {
     // protect against missing data
-    $needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1);
+    $needed_parts = array('nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1);
     $data = array();
     $keys = implode('|', array_keys($needed_parts));
 
@@ -704,8 +775,7 @@
     // remove unwanted characters
     $text = preg_replace('~[^-\w]+~', '', $text);
 
-    if (empty($text))
-    {
+    if (empty($text)) {
         return 'n-a';
     }
 
@@ -714,80 +784,82 @@
 
 
 // from http://www.house6.com/blog/?p=83
-function sanitize_filename($f) {
+function sanitize_filename($f)
+{
     // a combination of various methods
     // we don't want to convert html entities, or do any url encoding
     // we want to retain the "essence" of the original file name, if possible
     // char replace table found at:
     // http://www.php.net/manual/en/function.strtr.php#98669
     $replace_chars = array(
-            'Š'=>'S', 'š'=>'s', 'Ð'=>'Dj','Ž'=>'Z', 'ž'=>'z', 'À'=>'A', 'Á'=>'A', 'Â'=>'A', 'Ã'=>'A', 'Ä'=>'A',
-            'Å'=>'A', 'Æ'=>'A', 'Ç'=>'C', 'È'=>'E', 'É'=>'E', 'Ê'=>'E', 'Ë'=>'E', 'Ì'=>'I', 'Í'=>'I', 'Î'=>'I',
-            'Ï'=>'I', 'Ñ'=>'N', 'Ò'=>'O', 'Ó'=>'O', 'Ô'=>'O', 'Õ'=>'O', 'Ö'=>'O', 'Ø'=>'O', 'Ù'=>'U', 'Ú'=>'U',
-            'Û'=>'U', 'Ü'=>'U', 'Ý'=>'Y', 'Þ'=>'B', 'ß'=>'Ss','à'=>'a', 'á'=>'a', 'â'=>'a', 'ã'=>'a', 'ä'=>'a',
-            'å'=>'a', 'æ'=>'a', 'ç'=>'c', 'è'=>'e', 'é'=>'e', 'ê'=>'e', 'ë'=>'e', 'ì'=>'i', 'í'=>'i', 'î'=>'i',
-            'ï'=>'i', 'ð'=>'o', 'ñ'=>'n', 'ò'=>'o', 'ó'=>'o', 'ô'=>'o', 'õ'=>'o', 'ö'=>'o', 'ø'=>'o', 'ù'=>'u',
-            'ú'=>'u', 'û'=>'u', 'ý'=>'y', 'ý'=>'y', 'þ'=>'b', 'ÿ'=>'y', 'ƒ'=>'f'
+        'Š' => 'S', 'š' => 's', 'Ð' => 'Dj', 'Ž' => 'Z', 'ž' => 'z', 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A',
+        'Å' => 'A', 'Æ' => 'A', 'Ç' => 'C', 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I',
+        'Ï' => 'I', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U',
+        'Û' => 'U', 'Ü' => 'U', 'Ý' => 'Y', 'Þ' => 'B', 'ß' => 'Ss', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a',
+        'å' => 'a', 'æ' => 'a', 'ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i',
+        'ï' => 'i', 'ð' => 'o', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ø' => 'o', 'ù' => 'u',
+        'ú' => 'u', 'û' => 'u', 'ý' => 'y', 'ý' => 'y', 'þ' => 'b', 'ÿ' => 'y', 'ƒ' => 'f'
     );
     $f = strtr($f, $replace_chars);
     // convert & to "and", @ to "at", and # to "number"
     $f = preg_replace(array('/[\&]/', '/[\@]/', '/[\#]/'), array('-and-', '-at-', '-number-'), $f);
-    $f = preg_replace('/[^(\x20-\x7F)]*/','', $f); // removes any special chars we missed
+    $f = preg_replace('/[^(\x20-\x7F)]*/', '', $f); // removes any special chars we missed
     $f = str_replace(' ', '-', $f); // convert space to hyphen
     $f = str_replace('\'', '', $f); // removes apostrophes
     $f = preg_replace('/[^\w\-\.]+/', '', $f); // remove non-word chars (leaving hyphens and periods)
     $f = preg_replace('/[\-]+/', '-', $f); // converts groups of hyphens into one
-    if (function_exists('iconv'))
-    {
+    if (function_exists('iconv')) {
         $f = iconv('utf-8', 'us-ascii//TRANSLIT', $f);
     }
 
     return strtolower($f);
 }
 
-function rgb2hex($rgb) {
-   $hex = "#";
-   $hex .= str_pad(dechex($rgb[0]), 2, "0", STR_PAD_LEFT);
-   $hex .= str_pad(dechex($rgb[1]), 2, "0", STR_PAD_LEFT);
-   $hex .= str_pad(dechex($rgb[2]), 2, "0", STR_PAD_LEFT);
+function rgb2hex($rgb)
+{
+    $hex = "#";
+    $hex .= str_pad(dechex($rgb[0]), 2, "0", STR_PAD_LEFT);
+    $hex .= str_pad(dechex($rgb[1]), 2, "0", STR_PAD_LEFT);
+    $hex .= str_pad(dechex($rgb[2]), 2, "0", STR_PAD_LEFT);
 
-   return $hex; // returns the hex value including the number sign (#)
+    return $hex; // returns the hex value including the number sign (#)
 }
 
-function hsl2Rgb( $h, $s, $l ){
-    $r;
-    $g;
-    $b;
-	$c = ( 1 - abs( 2 * $l - 1 ) ) * $s;
-	$x = $c * ( 1 - abs( fmod( ( $h / 60 ), 2 ) - 1 ) );
-	$m = $l - ( $c / 2 );
-	if ( $h < 60 ) {
-		$r = $c;
-		$g = $x;
-		$b = 0;
-	} else if ( $h < 120 ) {
-		$r = $x;
-		$g = $c;
-		$b = 0;
-	} else if ( $h < 180 ) {
-		$r = 0;
-		$g = $c;
-		$b = $x;
-	} else if ( $h < 240 ) {
-		$r = 0;
-		$g = $x;
-		$b = $c;
-	} else if ( $h < 300 ) {
-		$r = $x;
-		$g = 0;
-		$b = $c;
-	} else {
-		$r = $c;
-		$g = 0;
-		$b = $x;
-	}
-	$r = ( $r + $m ) * 255;
-	$g = ( $g + $m ) * 255;
-	$b = ( $b + $m  ) * 255;
-    return array( floor( $r ), floor( $g ), floor( $b ) );
+function hsl2Rgb($h, $s, $l)
+{
+    $r = -1;
+    $g = -1;
+    $b = -1;
+    $c = (1 - abs(2 * $l - 1)) * $s;
+    $x = $c * (1 - abs(fmod(($h / 60), 2) - 1));
+    $m = $l - ($c / 2);
+    if ($h < 60) {
+        $r = $c;
+        $g = $x;
+        $b = 0;
+    } else if ($h < 120) {
+        $r = $x;
+        $g = $c;
+        $b = 0;
+    } else if ($h < 180) {
+        $r = 0;
+        $g = $c;
+        $b = $x;
+    } else if ($h < 240) {
+        $r = 0;
+        $g = $x;
+        $b = $c;
+    } else if ($h < 300) {
+        $r = $x;
+        $g = 0;
+        $b = $c;
+    } else {
+        $r = $c;
+        $g = 0;
+        $b = $x;
+    }
+    $r = ($r + $m) * 255;
+    $g = ($g + $m) * 255;
+    $b = ($b + $m) * 255;
+    return array(floor($r), floor($g), floor($b));
 }