]> _ Git - fluidbook-toolbox.git/commitdiff
wip #5661 @2
authorVincent Vanwaelscappel <vincent@cubedesigners.com>
Mon, 19 Dec 2022 15:24:59 +0000 (16:24 +0100)
committerVincent Vanwaelscappel <vincent@cubedesigners.com>
Mon, 19 Dec 2022 15:24:59 +0000 (16:24 +0100)
21 files changed:
app/Console/Commands/FluidbookFarmPing.php
app/Fluidbook/Farm.php [new file with mode: 0644]
app/Fluidbook/Links.php [new file with mode: 0644]
app/Fluidbook/Packager/Base.php [new file with mode: 0644]
app/Fluidbook/Packager/ChromeOS.php [new file with mode: 0644]
app/Fluidbook/Packager/MacOS.php [new file with mode: 0644]
app/Fluidbook/Packager/OfflineHTML.php [new file with mode: 0644]
app/Fluidbook/Packager/Online.php [new file with mode: 0644]
app/Fluidbook/Packager/Precompiled.php [new file with mode: 0644]
app/Fluidbook/Packager/Scorm.php [new file with mode: 0644]
app/Fluidbook/Packager/Sharepoint.php [new file with mode: 0644]
app/Fluidbook/Packager/USBKey.php [new file with mode: 0644]
app/Fluidbook/Packager/WindowsEXE.php [new file with mode: 0644]
app/Fluidbook/Packager/WindowsInstaller.php [new file with mode: 0644]
app/Fluidbook/Packager/WindowsZIP.php [new file with mode: 0644]
app/Http/Controllers/Admin/Operations/FluidbookPublication/EditOperation.php
app/Jobs/FluidbookCompiler.php
app/Models/FluidbookDocument.php
app/Models/FluidbookPublication.php
app/Util/FluidbookFarm.php [deleted file]
app/Util/FluidbookLinks.php [deleted file]

index bdbf351feb5ab20925aee7206d36d8a9766c109d..d7ef3c62bd13d8b4bff4c6778c7aa07b1963361e 100644 (file)
@@ -3,7 +3,7 @@
 
 namespace App\Console\Commands;
 
-use App\Util\FluidbookFarm;
+use App\Fluidbook\Farm;
 use Cubist\Backpack\Console\Commands\CubistCommand;
 
 
@@ -14,6 +14,6 @@ class FluidbookFarmPing extends CubistCommand
 
     public function handle()
     {
-        FluidbookFarm::ping(true, $this->option('force', false));
+        Farm::ping(true, $this->option('force', false));
     }
 }
diff --git a/app/Fluidbook/Farm.php b/app/Fluidbook/Farm.php
new file mode 100644 (file)
index 0000000..b14290d
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+
+namespace App\Fluidbook;
+
+use Cubist\Util\Files\Files;
+use hollodotme\FastCGI\Client;
+use hollodotme\FastCGI\Requests\PostRequest;
+use hollodotme\FastCGI\SocketConnections\NetworkSocket;
+use function App\Util\is_countable;
+
+class Farm
+{
+    protected static $_farmServers = [
+        ['name' => 'alphaville', 'host' => 'fluidbook-processfarm', 'port' => 9000, 'weight' => 24],
+        ['name' => 'brazil', 'host' => 'brazil.cubedesigners.com', 'weight' => 6],
+        ['name' => 'clockwork', 'host' => 'clockwork.cubedesigners.com', 'weight' => 2],
+        ['name' => 'dracula', 'host' => 'dracula.cubedesigners.com', 'weight' => 3],
+        ['name' => 'elephantman', 'host' => 'elephantman.cubedesigners.com', 'weight' => 1],
+        ['name' => 'fastandfurious', 'host' => 'fastandfurious.cubedesigners.com', 'weight' => 1],
+        ['name' => 'godzilla', 'host' => 'godzilla.cubedesigners.com', 'weight' => 3],
+        ['name' => 'her', 'host' => 'her2.cubedesigners.com', 'weight' => 4],
+        ['name' => 'isleofdogs', 'host' => 'paris.cubedesigners.com', 'port' => 9458, 'weight' => 2],
+        ['name' => 'jumanji', 'host' => 'paris.cubedesigners.com', 'port' => 9459, 'weight' => 2],
+    ];
+
+    protected static function _pingCache()
+    {
+        return Files::mkdir(storage_path('fluidbookfarm')) . '/pings';
+    }
+
+    protected static function _serversCache()
+    {
+        return Files::mkdir(storage_path('fluidbookfarm')) . '/servers';
+    }
+
+    public static function getServers()
+    {
+        return self::$_farmServers;
+    }
+
+    public static function pickOneServer()
+    {
+        $hat = [];
+        $pingCache = self::_pingCache();
+        if (!file_exists($pingCache)) {
+            self::ping(false);
+        }
+        $pings = json_decode(file_get_contents(self::_pingCache()));
+
+        foreach (self::$_farmServers as $k => $farmServer) {
+            if (!isset($pings[$k]) || !$pings[$k]) {
+                continue;
+            }
+            for ($i = 0; $i < $farmServer['weight']; $i++) {
+                $hat[] = $k;
+            }
+        }
+        shuffle($hat);
+        $i = array_pop($hat);
+        return self::$_farmServers[$i];
+    }
+
+    public static function getFCGIConnexion(array $farm, $timeout = 240): NetworkSocket
+    {
+        $timeout *= 1000;
+        return new NetworkSocket($farm['host'], $farm['port'] ?? 9457, $timeout, $timeout);
+    }
+
+    public static function sendRequest($farmer, $url, $params = [], $timeout = 240)
+    {
+        set_time_limit(0);
+        $client = new Client();
+        $response = $client->sendRequest(self::getFCGIConnexion($farmer, $timeout), new PostRequest($url, http_build_query($params)));
+        return trim($response->getBody());
+    }
+
+    public static function getFile($page, $format, $resolution, $withText, $withGraphics, $version, $resolutionRatio, $mobileFirstRatio, $path, $force = false)
+    {
+        $start = microtime(true);
+        $farmer = self::pickOneServer();
+
+        $params = ['page' => $page, 'format' => $format, 'resolution' => $resolution, 'withText' => $withText, 'withGraphics' => $withGraphics, 'version' => $version, 'force' => $force, 'out' => $path, 'resolutionRatio' => $resolutionRatio, 'mobileRatio' => $mobileFirstRatio];
+
+        $output = self::sendRequest($farmer, 'process.php', $params);
+        if (preg_match('|/data1/extranet/www/[^\s]+|', $output, $matches)) {
+            $o = $matches[0];
+        } else {
+            $o = $output;
+        }
+
+        if (file_exists($o)) {
+            $res = $o;
+        } else {
+            echo $o;
+            $res = false;
+        }
+
+        $time = round(microtime(true) - $start, 4);
+        $log = '[' . $farmer['name'] . ']' . "\t" . date('Y-m-d H:i:s') . "\t" . $time . "\t$page|$format|$resolution|$withText|$withGraphics|$version\t$res\t" . $output . "\n";
+
+        error_log($log);
+
+        return $res;
+    }
+
+    public static function ping($echo = true, $force = false)
+    {
+        $cache = self::_pingCache();
+        $servers = self::getServers();
+        $pings = [];
+        if (file_exists($cache)) {
+            $cached = json_decode(file_get_contents($cache));
+            if (is_countable($cached) && count($cached) === count($servers)) {
+                $pings = $cached;
+            }
+        }
+
+        foreach ($servers as $id => $farmer) {
+            if ($echo) {
+                echo $farmer['name'] . ' (' . $id . ') || ';
+            }
+            if (isset($pings[$id]) && !$pings[$id]) {
+                // If ping failed recently, we wait a bit before trying again.
+                if (!$force && rand(0, 9) != 5) {
+                    if ($echo) {
+                        echo 'Skipped, will try again soon' . "\n";
+                    }
+                    continue;
+                }
+            }
+            try {
+                $res = self::sendRequest($farmer, 'ping.php', [], 5);
+                $ok = $res == '1';
+            } catch (\Exception $e) {
+                $res = $e->getMessage();
+                $ok = false;
+            }
+
+            if ($echo) {
+                echo ($ok ? 'OK' : 'KO') . ' : ' . trim($res) . "\n";
+            }
+
+            $pings[$id] = $ok;
+        }
+        file_put_contents($cache, json_encode($pings));
+        file_put_contents(self::_serversCache(), json_encode($servers));
+    }
+}
diff --git a/app/Fluidbook/Links.php b/app/Fluidbook/Links.php
new file mode 100644 (file)
index 0000000..77d4116
--- /dev/null
@@ -0,0 +1,476 @@
+<?php
+
+namespace App\Fluidbook;
+
+use App\Models\User;
+use App\Util\wsDAOBook;
+use App\Util\wsDocument;
+use Cubist\Util\Files\Files;
+use Cubist\Util\Str;
+use Fluidbook\Tools\Links\Link;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Style\Alignment;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use function App\Util\utf8_encode;
+
+class Links
+{
+    protected static $_testLinkCache = null;
+    protected static $_linksKey = null;
+
+    /**
+     * @throws Exception
+     */
+    public static function linksToExcel($links, $rulers, $pages = null)
+    {
+        set_time_limit(0);
+
+        $cols = array(
+            'uid' => __('Identifiant unique'),
+            'page' => __('Page de la publication'), 'left' => __('x'), 'top' => __('y'), 'width' => __('Largeur'), 'height' => __('Hauteur'), 'rot' => __('Rotation'),
+            'type' => __('Type'), 'to' => __('Destination'), 'target' => __('Cible'),
+            'infobulle' => __('Infobulle'), 'numerotation' => __('Numérotation'),
+            'display_area' => __('Activer la surbrillance'),
+            'video_loop' => __('Video : boucle'), 'video_auto_start' => __('Video : démarrage automatique'), 'video_controls' => __('Vidéo : afficher les contrôles'), 'video_sound_on' => __('Vidéo : activer le son'),
+            'inline' => __('Vidéo : afficher dans la page'), 'video_width' => __('Vidéo : Largeur du popup'), 'video_height' => __('Vidéo : Hauteur du popup'),
+            'interactive' => __('Interactivité'), 'video_service' => __('Webvideo : service'),
+            'extra' => __('Paramètre supplémentaire'),
+            'alternative' => __('Alternative'),
+            'read_mode' => __('Mode de lecture'),
+            'image' => __('Image'), 'image_rollover' => __('Animation au survol'),
+            'animation' => __('Animation'),
+            'group' => __('Groupe'),
+            'zindex' => __('Profondeur'),
+        );
+
+        $comments = array();
+
+        $xls = new Spreadsheet();
+        $s = $xls->setActiveSheetIndex(0);
+        $s->setTitle('Links');
+
+        // Labels
+        $i = 1;
+        foreach ($cols as $id => $label) {
+            $s->setCellValueByColumnAndRow($i, 1, $id);
+            $s->getColumnDimensionByColumn($i)->setAutoSize(true);
+            $s->getStyleByColumnAndRow($i, 1)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
+            $i++;
+        }
+
+        // Links
+        self::_correctImageSpecialLinks($links);
+        $j = 2;
+        foreach ($links as $l) {
+            $i = 1;
+            foreach ($cols as $id => $label) {
+                if (($id == 'document_id' || $id == 'document_page')) {
+                    if (!is_null($pages)) {
+                        $infos = $pages[$l['page']];
+                        $value = $infos[$id];
+                    } else {
+                        $value = '';
+                    }
+                } else {
+
+                    if (isset($l[$id])) {
+                        if (is_bool($l[$id])) {
+                            $l[$id] = $l[$id] ? '1' : '0';
+                        }
+                        if ($id === 'numerotation') {
+                            if ($l[$id] === 'false') {
+                                $l[$id] = 'physical';
+                            }
+                        }
+                        if ($id === 'to') {
+                            $s->getCellByColumnAndRow($i, $j)->setDataType(DataType::TYPE_STRING)->getStyle()->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_TEXT);
+                        }
+                        $value = $l[$id];
+                    } else {
+                        $value = '';
+                    }
+                }
+
+                $s->setCellValueExplicitByColumnAndRow($i, $j, $value, DataType::TYPE_STRING);
+                $i++;
+            }
+            $j++;
+        }
+        // Rulers
+        $s = $xls->createSheet();
+        $s->setTitle('Rulers');
+
+        $rcols = array('page', 'type', 'pos');
+        $i = 1;
+        // Labels
+        foreach ($rcols as $id) {
+            $s->setCellValueByColumnAndRow($i, 1, $id);
+            $s->getColumnDimensionByColumn($i)->setAutoSize(true);
+            $i++;
+        }
+
+        // Contents
+        $j = 2;
+        foreach ($rulers as $r) {
+            $i = 1;
+            foreach ($rcols as $id) {
+                if (!is_null($pages) && ($id == 'document_id' || $id == 'document_page')) {
+                    $infos = $pages[$r['page']];
+                    $value = $infos[$id];
+                } else {
+                    $value = $r[$id];
+                }
+                $s->setCellValueByColumnAndRow($i, $j, $value);
+                $s->getStyleByColumnAndRow($i, $j)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
+                $i++;
+            }
+            $j++;
+        }
+
+        $xls->setActiveSheetIndex(0);
+        return $xls;
+    }
+
+
+    public static function getLinksAndRulers($book_id, &$links, &$rulers, $time = 'latest')
+    {
+        if (null === $time) {
+            $time = 'latest';
+        }
+        $dir = self::getLinksDir($book_id);
+
+        $file = $dir . '/' . $time . '.links3.gz';
+        if ($time === 'latest' && !file_exists($file)) {
+            $versions = self::getLinksVersions($book_id);
+            foreach ($versions as $version => $m) {
+                copy(Files::firstThatExists($dir . '/' . $version . '.links3.gz', $dir . '/' . $version . '.links.gz'), $dir . '/latest.links3.gz');
+                copy(Files::firstThatExists($dir . '/' . $version . '.meta3.gz', $dir . '/' . $version . '.meta.gz'), $dir . '/latest.meta3.gz');
+                break;
+            }
+        }
+        if (!file_exists($file)) {
+            $links = [];
+            $rulers = [];
+            return;
+        }
+
+        $r = json_decode(gzdecode(file_get_contents($file)), true);
+        $links = self::_UID($r['links']);
+        $rulers = self::_UID($r['rulers']);
+        if (can('fluidbook-publication:links:edit-animations')) {
+            $links = Link::decryptLinks($links);
+        }else{
+            $links = Link::encryptLinks($links);
+        }
+
+        self::_correctImageSpecialLinks($links);
+    }
+
+    protected static function _UID($items)
+    {
+        $res = [];
+        foreach ($items as $item) {
+            if (!isset($item['uid'])) {
+                $item['uid'] = self::uid();
+            }
+            $res[$item['uid']] = $item;
+        }
+        return $res;
+    }
+
+    protected static function uid()
+    {
+        return Str::lower(Str::random(12));
+    }
+
+    protected static function _correctImageSpecialLinks(&$links)
+    {
+        foreach ($links as $k => $link) {
+            if (preg_match('/^link_(.*)$/', $link['page'], $matches) && strlen($matches[1]) !== 32) {
+                $uid = $matches[1];
+                foreach ($links as $l) {
+                    if ($l['uid'] === $uid && $l['alternative']) {
+                        $links[$k]['page'] = 'link_' . md5($l['alternative']);
+                        break;
+                    }
+                }
+            } else if (preg_match('/^([0-9a-f]{32})$/', $link['page'], $matches)) {
+                $links[$k]['page'] = 'link_' . $matches[1];
+            }
+        }
+    }
+
+    public static function getLinksFromExcel($xls, &$links, &$rulers)
+    {
+        $s = $xls->setActiveSheetIndexByName('Links');
+        $i = 0;
+        $links = array();
+        foreach ($s->getRowIterator() as $row) {
+            $cellIterator = $row->getCellIterator();
+            $cellIterator->setIterateOnlyExistingCells(false);
+            if ($i == 0) {
+                $cols = array();
+                foreach ($cellIterator as $cell) {
+                    $cols[] = $cell->getValue();
+                }
+            } else {
+                $link = array();
+                $j = 0;
+                foreach ($cellIterator as $cell) {
+                    $link[$cols[$j]] = $cell->getValue();
+                    $j++;
+                }
+                if ($link['display_area'] == '' || !$link['display_area']) {
+                    $link['display_area'] = '0';
+                }
+                if (trim($link['infobulle']) == '') {
+                    $link['infobulle'] = '';
+                }
+                $links[] = $link;
+            }
+
+            $i++;
+        }
+
+        $i = 0;
+        $rulers = array();
+        $s = $xls->setActiveSheetIndexByName('Rulers');
+        foreach ($s->getRowIterator() as $row) {
+            $cellIterator = $row->getCellIterator();
+            $cellIterator->setIterateOnlyExistingCells(false);
+            if ($i == 0) {
+                $cols = array();
+                foreach ($cellIterator as $cell) {
+                    $cols[] = $cell->getValue();
+                }
+            } else {
+                $link = array();
+                $j = 0;
+                foreach ($cellIterator as $cell) {
+                    $ruler[$cols[$j]] = $cell->getValue();
+                    $j++;
+                }
+
+                $rulers[] = $ruler;
+            }
+            $i++;
+        }
+
+        self::_correctImageSpecialLinks($links);
+    }
+
+    public static function getLinksFromAutobookmarkText($txt, &$links, &$rulers)
+    {
+        $links = array();
+        $rulers = array();
+
+        $lines = explode("\n", $txt);
+
+        $protocols = array('mailto' => 3, 'custom' => 7, 'cart' => 12, 'pagelabel' => 26);
+
+        foreach ($lines as $line) {
+            $line = trim($line);
+            if ($line == '') {
+                continue;
+            }
+            if (strpos('#', $line) === 0) {
+                continue;
+            }
+            $target = $numerotation = '';
+            list($page, $left, $top, $width, $height, $type, $to) = explode(';', $line);
+            if ($type <= 2) {
+                $target = '_blank';
+            } elseif ($type == 5) {
+                $numerotation = 'physical';
+            }
+
+            $links[] = array(
+                'page' => $page,
+                'left' => $left, 'top' => $top, 'width' => $width, 'height' => $height, 'rot' => '',
+                'type' => $type, 'to' => $to, 'target' => $target,
+                'infobulle' => '', 'numerotation' => $numerotation, 'display_area' => '1');
+        }
+
+        self::_correctImageSpecialLinks($links);
+    }
+
+    public static function saveLinksInFile($book_id, $user_id, $comments, $links, $rulers = [], $specialLinks = [], $specialRulers = [])
+    {
+        $lr = self::mergeLinksAndRulers($links, $rulers, $specialLinks, $specialRulers);
+        $meta = ['links' => count($lr['links']), 'rulers' => count($lr['rulers']), 'comments' => $comments, 'user' => $user_id];
+        $base = self::getLinksDir($book_id) . '/' . time();
+        $latestLinks = self::getLinksDir($book_id) . '/latest.links3.gz';
+        $latestMeta = self::getLinksDir($book_id) . '/latest.meta3.gz';
+        file_put_contents($base . '.meta3.gz', gzencode(json_encode($meta)));
+        file_put_contents($base . '.links3.gz', gzencode(json_encode($lr)));
+        copy($base . '.links3.gz', $latestLinks);
+        copy($base . '.meta3.gz', $latestMeta);
+    }
+
+
+    public static function getLinksDir($book_id)
+    {
+        return Files::mkdir('/data/extranet/www/fluidbook/books/links/' . $book_id);
+    }
+
+    public static function getLinksVersions($book_id)
+    {
+        $dir = self::getLinksDir($book_id);
+        $dr = opendir($dir);
+        $updates = [];
+        while ($f = readdir($dr)) {
+            if ($f === '.' || $f === '..') {
+                continue;
+            }
+            $e = explode('.', $f, 2);
+            if (($e[1] !== 'meta.gz' && $e[1] !== 'meta3.gz') || $e[0] === 'latest') {
+                continue;
+            }
+
+            $updates[$e[0]] = self::getMeta($book_id, $e[0]);
+        }
+        krsort($updates);
+
+
+        $res = [];
+        foreach ($updates as $timestamp => $u) {
+            try {
+                $u['name'] = User::find($u['user'])->name;
+            } catch (\Exception $e) {
+                $u['name'] = '-';
+            }
+            $u['date'] = date('Y-m-d H:i:s', $timestamp);
+            $u['timestamp'] = $timestamp;
+            $res[] = $u;
+        }
+
+        return $res;
+    }
+
+    public static function getMeta($book_id, $update = 'latest')
+    {
+        return json_decode(gzdecode(file_get_contents(Files::firstThatExists(self::getLinksDir($book_id) . '/' . $update . '.meta3.gz', self::getLinksDir($book_id) . '/' . $update . '.meta.gz'))), true);
+    }
+
+    public static function mergeLinksAndRulers($links, $rulers, $specialLinks, $specialRulers)
+    {
+        $finalLinks = [];
+        $l = array_merge(self::_getAsArray($links), self::_getAsArray($specialLinks));
+
+        $k = 0;
+        foreach ($l as $item) {
+            $item['id'] = $k + 1;
+            if (!isset($item['to'])) {
+                $item['to'] = '';
+            }
+            $finalLinks[] = $item;
+            $k++;
+        }
+
+        self::_correctImageSpecialLinks($finalLinks);
+
+        return ['links' => Link::encryptLinks($finalLinks), 'rulers' => array_merge(self::_getAsArray($rulers), self::_getAsArray($specialRulers))];
+    }
+
+    protected static function _getAsArray($v)
+    {
+        if (is_array($v)) {
+            return $v;
+        }
+        return json_decode($v, true);
+    }
+
+    public static function addLinksFromPDF($book_id)
+    {
+        global $core;
+
+        $daoBook = new wsDAOBook($core->con);
+        $pages = $daoBook->getPagesOfBook($book_id);
+
+        $booleans = array('video_loop', 'video_auto_start', 'video_controls', 'video_sound_on');
+        $numbers = ['left', 'top', 'width', 'height'];
+
+        $links = [];
+
+        foreach ($pages as $page => $info) {
+            $csv = wsDocument::getDir($info['document_id']) . '/p' . $info['document_page'] . '.csv';
+            if (!file_exists($csv) && file_exists($csv . '.gz')) {
+                $csv = 'compress.zlib://' . $csv . '.gz';
+            } elseif (!file_exists($csv)) {
+                continue;
+            }
+
+            $newformat = (filemtime($csv) > 1363685416);
+
+            $fp = fopen($csv, 'rb');
+
+            while (true) {
+                $line = fgetcsv($fp, 512, ';', '"');
+                // End of file
+                if (!$line) {
+                    break;
+                }
+
+                // Commentaire || ligne vide
+                if (substr($line[0], 0, 1) == '#' || is_null($line[0])) {
+                    continue;
+                }
+
+                $link = [];
+                if ($newformat) {
+                    $cols = array('page' => '', 'left' => '', 'top' => '', 'width' => '', 'height' => '', 'type' => '', 'to' => '', 'target' => '_blank', 'video_loop' => true, 'video_auto_start' => true, 'video_controls' => true, 'video_sound_on' => true, 'infobulle' => '', 'numerotation' => 'physical', "inline" => true);
+                } else {
+                    $cols = array('page' => '', 'type' => '', 'to' => '', 'left' => '', 'top' => '', 'width' => '', 'height' => '', 'target' => '_blank', 'video_loop' => true, 'video_auto_start' => true, 'video_controls' => true, 'video_sound_on' => true, 'infobulle' => '', 'numerotation' => 'physical');
+                }
+
+
+                $k = 0;
+                foreach ($cols as $col => $default) {
+                    if (isset($line[$k])) {
+                        if (in_array($k, $numbers)) {
+                            $link[$col] = (float)str_replace(',', '.', $line[$k]);
+                        } else if (in_array($k, $booleans)) {
+                            $link[$col] = ($line[$k] == '1');
+                        } else {
+                            $link[$col] = utf8_encode($line[$k]);
+                        }
+                    } else {
+                        $link[$col] = $default;
+                    }
+                    $k++;
+                }
+
+                if ($link['type'] == 18) {
+                    $link['infobulle'] = $link['to'];
+                    $link['to'] = '';
+                }
+
+                $link['display_area'] = '1';
+                $link['page'] = $page;
+                $links[] = $link;
+            }
+
+        }
+
+        self::saveLinksInFile($book_id, $core->user->utilisateur_id, 'Links imported from PDF', $links, []);
+    }
+
+    public static function getLinksAndRulersFromExcelFile($path, &$links, &$rulers)
+    {
+        $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
+        $xls = $reader->load($path);
+        Links::getLinksFromExcel($xls, $links, $rulers);
+    }
+
+    public static function generateUID()
+    {
+        $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
+        $randstring = '';
+        for ($i = 0; $i < 12; $i++) {
+            $randstring = $characters[rand(0, 35)];
+        }
+        return $randstring;
+    }
+}
diff --git a/app/Fluidbook/Packager/Base.php b/app/Fluidbook/Packager/Base.php
new file mode 100644 (file)
index 0000000..7cb7397
--- /dev/null
@@ -0,0 +1,295 @@
+<?php
+
+namespace App\Fluidbook\Packager;
+
+use Cubist\Util\CommandLine;
+use Cubist\Util\PHP;
+
+class Base extends \App\Jobs\Base
+{
+
+    protected $dir;
+    protected $vdir;
+    public $book;
+    protected $pages;
+    protected $theme;
+    protected $version;
+    protected $book_id;
+    protected $themeRoot;
+    protected $daoBook;
+    protected $zip;
+    protected $workingDir;
+    protected $whole = true;
+    protected $_clean = true;
+    protected $_compileOnConstruct = false;
+    public $cleanOnDestruct = true;
+
+    public static function package($book_id, $version, $zip = true, $cleanOnDestruct = true, $options = [])
+    {
+        global $packager;
+
+        PHP::neverStop();
+
+
+        if ($version === 'html') {
+            $packager = new Online($book_id, null, true, $options);
+        } else if ($version === 'scorm') {
+            $packager = new SCORM($book_id, null, true, $options);
+        } else if ($version === 'sharepoint') {
+            $packager = new Sharepoint($book_id, null, true, $options);
+        } elseif ($version === 'win_html') {
+            $packager = new OfflineHTML($book_id, null, true, $options);
+        } else if ($version === 'win_exe_html') {
+            $packager = new WindowsZIP($book_id, $options);
+        } else if ($version === 'mac_exe_html') {
+            $packager = new MacOS($book_id, $options);
+        } else if ($version === 'win_ins_html') {
+            $packager = new WindowsInstaller($book_id, $options);
+        } else if ($version === 'win_inss_html') {
+            $packager = new WindowsEXE($book_id, $options);
+        } else if ($version === 'win_cd_html') {
+            $packager = new USBKey($book_id, $options);
+        } else if ($version === 'precompiled') {
+            $packager = new Precompiled($book_id, null, true, $options);
+        } else if ($version === 'chromeos') {
+            $packager = new ChromeOS($book_id, null, true, $options);
+        }
+        $packager->cleanOnDestruct = $packager->cleanOnDestruct && $cleanOnDestruct;
+
+        return $packager->makePackage($zip);
+    }
+
+    public function __construct($book_id, $vdir = null, $whole = true, $options = [])
+    {
+
+        $this->_clean = (null === $vdir);
+
+        $this->book_id = $book_id;
+
+        $this->vdir = $vdir;
+        $this->dir = ROOT . '/fluidbook/packager/' . $book_id . '/';
+        $this->whole = $whole;
+
+        if (!file_exists($this->dir)) {
+            mkdir($this->dir, 0777, true);
+        }
+
+        $this->daoBook = new wsDAOBook($core->con);
+        $this->book = $this->daoBook->selectById($book_id);
+        $forceCompile = false;
+        if (count($options)) {
+            $options['forceCompileOnDownload'] = true;
+        }
+        foreach ($options as $k => $v) {
+            $this->book->parametres->$k = $v;
+        }
+
+        $this->pages = $this->daoBook->getPagesOfBook($book_id, false);
+
+        $daoTheme = new wsDAOTheme($core->con);
+        $this->theme = $daoTheme->getThemeOfBook($book_id, true);
+        $this->themeRoot = WS_THEMES . '/' . $this->theme->theme_id . '/';
+
+        $this->workingDir = $this->book->getAssetDir();
+
+        if ($this->_compileOnConstruct) {
+            $this->compile($forceCompile);
+        }
+    }
+
+
+    protected function compile($forceCompile = false)
+    {
+        $this->daoBook->compile($this->book_id, '2', false, $this->book->parametres->forceCompileOnDownload || $forceCompile, false, $this->book);
+    }
+
+    protected function preparePackage()
+    {
+        $this->initTempDir();
+    }
+
+    public function makePackage($zip)
+    {
+        $this->preparePackage();
+    }
+
+    protected function replaceContents($str, $toReplace)
+    {
+        $res = $str;
+        foreach ($toReplace as $k => $v) {
+            if (is_null($v)) {
+                return;
+            }
+            $res = str_replace('$' . $k, $v, $res);
+        }
+        return $res;
+    }
+
+    protected function copyFluidbookFiles()
+    {
+        // Copie du FB vers un répertoire temporaire
+        $cp = new CommandLine('cp');
+        $cp->setArg('R');
+        $cp->setArg('p');
+        $cp->setArg(null, WS_BOOKS . '/final/' . $this->book->book_id . '/*');
+        $cp->setArg(null, $this->vdir);
+        $cp->execute();
+    }
+
+    protected function copyOtherFiles($files)
+    {
+        foreach ($files as $source => $dest) {
+            if (is_int($source)) {
+                $source = $dest;
+            }
+
+            $s = WS_COMPILE_ASSETS . '/' . $source;
+            if (is_file($s) && !file_exists($this->vdir . $dest)) {
+                $this->copy($s, $this->vdir . $dest);
+            } else if (is_dir($s)) {
+                $cp = new CommandLine('cp');
+                $cp->setArg('R');
+                $cp->setArg('p');
+                $cp->setArg(null, $s);
+                $cp->setArg(null, $this->vdir);
+                $cp->execute();
+
+                $mv = new CommandLine('mv');
+                $mv->setArg($this->vdir . '/' . $source);
+                $mv->setArg($this->vdir . '/' . $dest);
+                $mv->execute();
+            }
+        }
+    }
+
+    protected function getBaseFile()
+    {
+        return $this->version . '-' . date('Ymdhis') . '-' . $this->escapeTitle();
+    }
+
+    protected function escapeTitle()
+    {
+        $res = cubeText::str2URL($this->book->parametres->title);
+        if ($res == '') {
+            $res = 'fluidbook';
+        }
+        return $res;
+    }
+
+    protected function getRelativeBase()
+    {
+        return '/packager/download/' . $this->getBaseFile();
+    }
+
+    protected function getURLBase($ext = '')
+    {
+        $res = '/fluidbook' . $this->getRelativeBase();
+        if ($ext != '') {
+            $res .= '.' . $ext;
+        }
+        return $res;
+    }
+
+    protected function getPathBase($ext = '')
+    {
+        $res = WS_FILES . $this->getRelativeBase();
+        if ($ext != '') {
+            $res .= '.' . $ext;
+        }
+
+        return $res;
+    }
+
+    protected function zip($zipfile = null)
+    {
+        if (!$this->whole) {
+            return;
+        }
+        $url = $this->getURLBase('zip');
+        $final = $this->getPathBase('zip');
+        $rename = false;
+        if (is_null($zipfile)) {
+            $zipfile = $final;
+        } else {
+            $rename = true;
+        }
+
+        $dir = $this->getFinalPackageDir();
+        if (file_exists($dir)) {
+            $zip = new CommandLine('zip');
+            $zip->cd($dir);
+            $zip->setArg(null, $zipfile);
+            $zip->setArg('symlinks');
+            $zip->setArg('0');
+            $zip->setArg('r');
+            $zip->setArg('u');
+            $zip->setArg(null, '.');
+            $zip->setManualArg('-x "*/\.*"');
+            $zip->execute();
+            $zip->debug();
+        }
+
+        if (!file_exists(WS_FILES . '/packager/download')) {
+            mkdir(WS_FILES . '/packager/download', 0777, true);
+        }
+
+        if ($rename) {
+            rename($zipfile, $final);
+        }
+        return $url;
+    }
+
+    public function getFinalPackageDir()
+    {
+        $dir = $this->vdir;
+        $dir .= '/m/';
+        return $dir;
+    }
+
+    protected function initTempDir()
+    {
+        if (is_null($this->vdir)) {
+            $this->vdir = $this->dir . $this->version . '/';
+        }
+        $this->cleanVdir();
+        if (!file_exists($this->vdir . '/data')) {
+            mkdir($this->vdir . '/data', 0777, true);
+        }
+    }
+
+    protected function cleanVdir()
+    {
+        if (!$this->_clean) {
+            return;
+        }
+        if (file_exists($this->vdir)) {
+            // Suppression du répertoire si il existe
+            $rm = new CommandLine('rm');
+            $rm->setArg('r');
+            $rm->setArg('f');
+            $rm->setArg(null, $this->vdir);
+            $rm->execute();
+        }
+    }
+
+    protected function postPackage()
+    {
+
+    }
+
+    public function __destruct()
+    {
+        if ($this->whole && $this->cleanOnDestruct) {
+            $this->cleanVdir();
+        }
+    }
+
+    public function copy($source, $dest)
+    {
+        if (!file_exists($source)) {
+            return;
+        }
+        copy($source, $dest);
+        touch($dest, filemtime($source));
+    }
+}
diff --git a/app/Fluidbook/Packager/ChromeOS.php b/app/Fluidbook/Packager/ChromeOS.php
new file mode 100644 (file)
index 0000000..2f7068c
--- /dev/null
@@ -0,0 +1,50 @@
+<?php
+
+namespace App\Fluidbook\Packager;
+
+use Cubist\Util\CommandLine;
+
+class ChromeOS extends Online
+{
+    public function __construct($book_id, $vdir = null, $whole = true, $options = [])
+    {
+        parent::__construct($book_id, $vdir, $whole, $options);
+        $this->version = 'chromeos';
+        $this->cleanOnDestruct = true;
+    }
+
+    protected function preparePackage()
+    {
+        $res = parent::preparePackage();
+        $manifest = ['name' => $this->book->parametres->offlineTitle == '' ? $this->book->parametres->title : $this->book->parametres->offlineTitle,
+            'version' => '1.0.' . time(),
+            'manifest_version' => 3,
+            //'default_locale' => $this->book->lang,
+            //'icons' => [],
+            'permissions' => [
+                'webview',
+            ]
+        ];
+//        $sizes=[128,64,32,16];
+//        $pngFile = WS_THEMES . '/' . $this->theme->theme_id . '/' . $this->theme->parametres->favicon;
+//        foreach ($sizes as $size) {
+//
+//        }
+
+        file_put_contents($this->vdir . '/m/manifest.json', json_encode($manifest));
+        return $res;
+    }
+
+    public function makePackage($zip)
+    {
+        $this->preparePackage();
+
+        $chrome = new CommandLine('crx3');
+        $chrome->setArg('o', WS_PACKAGER . '/download/' . $this->getBaseFile() . '.crx');
+        $chrome->setArg(null, $this->vdir . '/m/');
+        $chrome->execute();
+        $chrome->debug();
+
+        return $this->getURLBase('crx');
+    }
+}
diff --git a/app/Fluidbook/Packager/MacOS.php b/app/Fluidbook/Packager/MacOS.php
new file mode 100644 (file)
index 0000000..42fb150
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+namespace App\Fluidbook\Packager;
+use Cubist\Util\CommandLine;
+
+class MacOS extends Offline
+{
+    protected $nwplatform = 'osx64';
+    protected $nwversion = '0.49.2';
+    protected $node_platform = 'mac';
+    protected $exenameMaxlength = 28;
+
+    public function __construct($book_id, $options = [])
+    {
+        parent::__construct($book_id, $options);
+        $this->version = 'mac_exe_html';
+    }
+
+    public function makePackage($zip)
+    {
+        $this->preparePackage();
+        $toDelete = ['chromedriver', 'credits.html', 'minidump_stackwalk', 'nwjc', 'payload', 'v8_context_snapshot.bin', 'natives_blob.bin', 'libffmpeg.dylib'];
+        foreach ($toDelete as $item) {
+            $p = $this->getFinalPackageDir() . '/' . $item;
+            `rm -rf "$p"`;
+        }
+        if ($zip) {
+            $res = $this->zip(null);
+        } else {
+            $res = $this->getFinalPackageDir();
+        }
+        $this->postPackage();
+        return $res;
+    }
+
+    public function getAppPath()
+    {
+        return $this->getFinalPackageDir() . '/' . $this->exeName . '.app';
+    }
+
+    public function getFinalPackageDir()
+    {
+        $res = parent::getFinalPackageDir();
+        return $res;
+    }
+
+    protected function preparePackage()
+    {
+        $this->initTempDir();
+        $this->copyFluidbookFiles();
+        $this->makeJSON();
+
+        $this->buildPath = WS_PACKAGER . '/nwbuild/' . $this->version . '/' . $this->book_id;
+        if (!file_exists($this->buildPath)) {
+            mkdir($this->buildPath, 0777, true);
+        }
+
+        $cl = new CommandLine('nwbuild');
+        $cl->setPath(CONVERTER_PATH);
+        $cl->setArg('p', $this->nwplatform);
+        $cl->setArg('o', $this->buildPath);
+        $cl->setArg('v', $this->nwversion);
+        $cl->setArg('winIco', $this->vdir . '/icon.ico');
+        $cl->setArg('macIcns', $this->vdir . '/icon.icns');
+        $cl->setArg(null, $this->vdir);
+        $cl->execute();
+        $cl->debug();
+
+        $this->replaceFFMpeg();
+        if(!file_exists($this->getAppPath())){
+            die('Error while building mac app : '.$cl->commande.' // '.$cl->output);
+        }
+
+        $this->signApp();
+    }
+
+    function replaceFFMpeg()
+    {
+        copy(WS_COMPILE_ASSETS . '/_exehtml/_ffmpeg/libffmpeg.dylib', $this->getAppPath() . '/Contents/Frameworks/nwjs Framework.framework/Versions/Current/libffmpeg.dylib');
+    }
+
+    protected function signApp()
+    {
+        self::_signApp($this->getAppPath());
+    }
+
+    public static function _signApp($appPath, $back = true)
+    {
+        $local_root = '/Users/vincent/Sign/';
+        $dist_root = '/mnt/sshfs/macparis' . $local_root;
+
+        $f = 'tmp_' . md5(rand(0, 1000000)) . ".app";
+        $path = $dist_root . $f;
+
+        // Copy app to mac
+        $cp = new CommandLine('cp');
+        $cp->setArg('r');
+        $cp->setArg(null, $appPath);
+        $cp->setArg(null, $path);
+        $cp->execute();
+        $cp->debug();
+
+        // Sign app
+        $cl = new CommandLine($local_root . 'sign');
+        $cl->setSSH(wsExporter::VINCENT, 'vincent', 'atacama', 22022);
+        $cl->setArg(null, $local_root . $f);
+        $cl->execute();
+        $cl->debug();
+        $res = $cl->output;
+
+        if ($back) {
+            // Copy back signed
+            $cp = new CommandLine('rsync');
+            $cp->setArg('r');
+            $cp->setArg('l');
+            $cp->setArg('p');
+            $cp->setArg('D');
+            $cp->setArg('v');
+            $cp->setArg(null, $path . '/');
+            $cp->setArg(null, $appPath . '/');
+            $cp->setArg('delete');
+            $cp->execute();
+            $cp->debug();
+        }
+
+        // `rm -rf $path`;
+        return $res;
+    }
+}
diff --git a/app/Fluidbook/Packager/OfflineHTML.php b/app/Fluidbook/Packager/OfflineHTML.php
new file mode 100644 (file)
index 0000000..22ff130
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+namespace App\Fluidbook\Packager;
+
+class OfflineHTML extends Online {
+       protected $_allowNetworking = 'internal';
+
+       protected function copyFluidbookFiles() {
+               parent::copyFluidbookFiles();
+               unlink($this->vdir . '/index.swf');
+               unlink($this->vdir . '/player.swf');
+               copy(WS_COMPILE_ASSETS . '/offline.swf', $this->vdir . '/index.swf');
+       }
+}
diff --git a/app/Fluidbook/Packager/Online.php b/app/Fluidbook/Packager/Online.php
new file mode 100644 (file)
index 0000000..b81c374
--- /dev/null
@@ -0,0 +1,441 @@
+<?php
+
+namespace App\Fluidbook\Packager;
+
+use Cubist\Util\CommandLine;
+
+class Online extends Base
+{   protected $origHTML;
+    protected $_labels = array();
+    protected $_allowNeworking = 'all';
+    protected $_ext = 'html';
+    protected $_disableScorm = true;
+    protected $_compileOnConstruct = false;
+
+    public function __construct($book_id, $vdir = null, $whole = true, $options = [])
+    {
+        parent::__construct($book_id, $vdir, $whole, $options);
+        $this->version = 'html';
+    }
+
+    protected function preparePackage()
+    {
+        parent::preparePackage();
+
+        if ($this->_disableScorm) {
+            $this->book->parametres->scorm_enable = false;
+        }
+
+        $this->_ext = $this->book->parametres->htmlExtension;
+        $this->book->parametres->actualHtmlExtension = $this->_ext;
+
+        $this->copyFluidbookFiles();
+        $this->mergeJavascript();
+
+//        $others = array('fluidbook.js', 'getflash.gif', 'index.html', 'style.css');
+//        $others = array_merge($others, $this->getSWFFiles());
+//
+//        $this->copyOtherFiles($others);
+
+        if (!file_exists($this->vdir . '/pages')) {
+            mkdir($this->vdir . '/pages/', 0777, true);
+        }
+
+        $ga = '';
+        if ($this->book->parametres->googleAnalyticsCustom) {
+            $ga = $this->book->parametres->googleAnalyticsCustom;
+        } elseif ($this->book->parametres->googleAnalytics != '') {
+            $variables = array('Language' => array('value' => 'getLang()', 'valueAsJS' => true, 'scope' => 2));
+            $ga = cubePage::googleAnalytics(explode(',', $this->book->parametres->googleAnalytics), true, $variables);
+        }
+
+        $statsfooter = '';
+        if ($this->book->parametres->statsCustom != '') {
+            $statsfooter = $this->book->parametres->statsCustom;
+        }
+
+        $facebook = '';
+        if ($this->book->parametres->facebook) {
+            if ($this->book->parametres->facebook_title != '') {
+                $facebook .= '<meta property="og:title" content="' . self::escape($this->book->parametres->facebook_title) . '" />';
+            } else {
+                $facebook .= '<meta property="og:title" content="' . self::escape($this->book->parametres->title) . '" />';
+            }
+            if ($this->book->parametres->facebook_description != '') {
+                $facebook .= '<meta property="og:description" content="' . self::escape($this->book->parametres->facebook_description) . '" />';
+            }
+            $t = 'https://workshop.fluidbook.com/services/facebook_thumbnail?cid=' . $this->book->cid . '&j=' . time();
+            $dim = getimagesize($t);
+            $facebook .= '<meta property="og:image" content="' . $t . '" />';
+            $facebook .= '<meta property="og:image:width" content="' . $dim[0] . '" />';
+            $facebook .= '<meta property="og:image:height" content="' . $dim[1] . '" />';
+        }
+
+        $favicon = '';
+
+        if ($this->theme->parametres->favicon != '') {
+            $favicon = '<link rel="shortcut icon" href="data/fluidbook.ico">';
+        }
+
+        $redirectPDF = 'redirectPDF();';
+        if ($this->book->parametres->mobileVersion == 'pdf') {
+            $redirectMobile = $redirectPDF;
+        } else {
+            $redirectMobile = 'redirectMobile();';
+            $this->prepareHTML5();
+        }
+
+        $seoVersion = true;
+        if (isset($this->book->parametres->seoVersion)) {
+            $seoVersion = $this->book->parametres->seoVersion;
+        }
+
+        $seoRobot = true;
+        if (isset($this->book->parametres->seoRobots)) {
+            $seoRobot = $this->book->parametres->seoRobots;
+        }
+
+        $robots = '';
+        if (!$seoRobot) {
+            $robots = '<meta name="robots" content="noindex, nofollow">';
+        }
+
+        $keywords = '';
+        if ($this->book->parametres->seoKeywords) {
+            $keywords = '<meta name="keywords" content="' . self::escape($this->book->parametres->seoKeywords) . '">';
+        }
+
+        $alwaysHTML5 = true;
+        $html5priority = true;
+
+
+        // Stuffs to replace in html
+        $toReplace = array('lang' => strtolower($this->book->lang),
+            'ga' => $ga,
+            'statsfooter' => $statsfooter,
+            'facebook' => $facebook,
+            'bgcolor' => $this->theme->parametres->loadingBackColor,
+            'junk' => TIME,
+            'robots' => $robots,
+            'favicon' => $favicon,
+            'alwaysHTML5' => $alwaysHTML5,
+            'keywords' => $keywords,
+        );
+
+        $this->origHTML = $this->book->parametres->htmlPrepend;
+        $h = $this->vdir . '/index.html';
+        if (file_exists($h)) {
+            $this->origHTML .= file_get_contents($h);
+            unlink($h);
+        }
+        $this->origHTML = $this->replaceHTML($toReplace);
+
+        $nav1 = $this->makeHTMLNav(true);
+        $nav = $this->makeHTMLNav(false);
+        $footer = $this->makeHTMLFooter();
+
+
+        foreach ($this->pages as $page => $infos) {
+            $pathToIndex = 'index.swf';
+            $pathToGetflash = 'getflash.gif';
+            $redirectScript = '';
+            if ($page == 1) {
+                $dest = 'index.' . $this->_ext;
+                $title = $this->book->parametres->title;
+                $sp = '';
+            } else {
+                $label = $this->_getLabelOfPage($page);
+                $title = $label . ' - ' . $this->book->parametres->title;
+                $dest = 'pages/' . $page . '-' . mb_strtolower(cubeText::str2URL($label)) . '.' . $this->_ext;
+                $pathToIndex = '../index.swf';
+                $pathToGetflash = '../getflash.gif';
+                $sp = '../';
+                $redirectScript = '<script type="text/javascript">window.location=\'../index.' . $this->_ext . '#/' . $page . '\';</script>';
+            }
+            $alt = '';
+
+//                     if ($seoVersion && CubeIT_Util_Gzip::file_exists($htmlfile)) {
+//                             $html = CubeIT_Util_Gzip::file_get_contents($htmlfile);
+//                             $alt .= "\n" . $html . "\n";
+//
+//                             if ($page == 1) {
+//                                     $alt .= $nav1;
+//                             } else {
+//                                     $alt .= $nav;
+//                             }
+//                     }
+
+            $alt .= $footer;
+
+            $base = '';
+            if ($this->book->parametres->baseUrl) {
+                $base = '<base href="' . $this->book->parametres->baseUrl . '" />';
+            }
+
+            if ($page == 1 && $this->book->parametres->seoDescription) {
+                $description = $this->book->parametres->seoDescription;
+            } else {
+                $textfile = wsDocument::getDir($infos['document_id']) . 'ph' . $infos['document_page'] . '.txt';
+                if (file_exists($textfile)) {
+                    $description = mb_substr(file_get_contents($textfile), 0, 150);
+                } else {
+                    $description = '';
+                }
+            }
+
+            $data = str_replace('$alt', $alt, $this->origHTML);
+            $data = str_replace('$base', $base, $data);
+            $data = str_replace('$pathToIndex', $pathToIndex, $data);
+            $data = str_replace('$title', $this->escape($title), $data);
+            $data = str_replace('$pathToGetflash', $pathToGetflash, $data);
+            $data = str_replace('$redirectScript', $redirectScript, $data);
+            $data = str_replace('$sp', $sp, $data);
+            $data = str_replace('$index_ext', $this->_ext, $data);
+            $data = str_replace('$description', '<meta name="description" content="' . self::escape($description, true) . '">', $data);
+
+            file_put_contents($this->vdir . $dest, $data);
+
+            if (!$seoVersion) {
+                break;
+            }
+        }
+    }
+
+    protected function getFlashvars()
+    {
+        return array();
+    }
+
+    public function prepareHTML5()
+    {
+        if (!$this->whole) {
+            return;
+        }
+        $dest = $this->vdir . 'm';
+
+        $mfid = $this->book->parametres->mobilefirstFluidbookId;
+        if ($mfid != '' && (int)$mfid > 0) {
+            $mfbook = $this->daoBook->selectById($mfid);
+            $this->_compileHTML5($this->book_id, $this->book, $dest . '/d', true);
+            $vars = wsDAOBook::$lastHTML5Compiler->getIndexVars();
+            $this->_compileHTML5($mfid, $mfbook, $dest . '/mf', true);
+
+            $hybrid = file_get_contents(WS_COMPILE_ASSETS . '/hybrid/index.html');
+            $replace = [
+                'titre' => $vars['<!-- $titre -->'],
+                'breakpoint' => $this->book->parametres->mobilefirstBreakpoint . 'px',
+                'bgcolor' => $this->theme->parametres->loadingBackColor,
+                'description' => $vars['<!-- $description -->'],
+                'twittercard' => $vars['<!-- $twittercard -->'],
+                'opengraph' => $vars['<!-- $opengraph -->'],
+                'credits' => $vars['<!-- $credits -->'],
+            ];
+            foreach ($replace as $var => $value) {
+                $hybrid = str_replace('$' . $var, $value, $hybrid);
+            }
+            file_put_contents($dest . '/index.html', $hybrid);
+        } else {
+            $this->_compileHTML5($this->book_id, $this->book, $dest);
+        }
+
+    }
+
+    protected function _compileHTML5($bookId, $book, $dest, $hybrid = false)
+    {
+
+        $this->daoBook->compile($bookId, 'html5', false, $book->parametres->forceCompileOnDownload, false, $book, true, false, $hybrid);
+
+        if (!file_exists($dest)) {
+            mkdir($dest, 0777, true);
+        }
+
+        $cp = new CommandLine('cp');
+        $cp->setPath(CONVERTER_PATH);
+        $cp->setArg('r');
+        $cp->setArg('p');
+        $cp->setArg(null, WS_BOOKS . '/html5/' . $bookId . '/*');
+        $cp->setArg(null, $dest);
+        $cp->execute();
+
+        $filesToAdd = array();
+        foreach ($filesToAdd as $f) {
+            $this->copy(WS_COMPILE_ASSETS . '/_html5/' . $f, $dest . '/' . $f);
+        }
+
+        $filesToDelete = array('indext.html', 'indexu.html');
+
+        $htmlFiles = array('index');
+
+        foreach ($htmlFiles as $name) {
+            $html = $book->parametres->htmlPrepend . file_get_contents($dest . '/' . $name . '.html');
+            file_put_contents($dest . '/' . $name . '.' . $this->_ext, $html);
+            if ($this->_ext != 'html') {
+                $filesToDelete[] = $name . '.html';
+            }
+        }
+
+        $rm = new CommandLine('rm');
+        $rm->setPath(CONVERTER_PATH);
+        foreach ($filesToDelete as $f) {
+            $rm->setArg(null, $dest . '/' . $f);
+        }
+        $rm->execute();
+
+        if ($this->_ext !== 'html') {
+            $e = $this->_ext;
+            `find $dest -type f -name "*.html" -exec rename 's/\.html$/.$e/' '{}' \;`;
+
+        }
+    }
+
+    public function makePackage($zip)
+    {
+        parent::makePackage($zip);
+        if ($zip) {
+            return $this->zip();
+        }
+        return $this->getFinalPackageDir();
+    }
+
+    protected function makeHTMLNav($root)
+    {
+        $res = '<nav>';
+        foreach ($this->pages as $page => $infos) {
+            $label = $this->_getLabelOfPage($page);
+            if ($page == 1) {
+                if ($root) {
+                    $url = 'index.' . $this->_ext;
+                } else {
+                    $url = '../index.' . $this->_ext;
+                }
+            } else {
+                if ($root) {
+                    $url = 'pages/' . $page . '-' . mb_strtolower(cubeText::str2URL($label)) . '.' . $this->_ext;
+                } else {
+                    $url = $page . '-' . mb_strtolower(cubeText::str2URL($label)) . '.' . $this->_ext;
+                }
+            }
+            $res .= '<a href="' . $url . '">' . $label . '</a>';
+        }
+        $res .= '</nav>';
+        return $res;
+    }
+
+    protected function _getLabelOfPage($page)
+    {
+        if (!isset($this->_chapters)) {
+            $this->_chapters = $this->book->chapters;
+        }
+
+
+        if (isset($this->_labels[$page])) {
+            return $this->_labels[$page];
+        }
+
+
+        if ($page == 1) {
+            $this->_labels[1] = $this->book->parametres->title;
+
+            return $this->_labels[1];
+        }
+
+
+        $virtual = $this->_getVirtualPage($page);
+
+        $candidates = array();
+        foreach ($this->_chapters as $c) {
+            if ($c->page == $virtual) {
+                $candidates[] = $c;
+            }
+        }
+        if (!count($candidates)) {
+            $this->_labels[$page] = $this->_getLabelOfPage($page - 1);
+            return $this->_labels[$page];
+        }
+
+        usort($candidates, array($this, '_sortCandidates'));
+        $c = array_shift($candidates);
+
+        $this->_labels[$page] = $c->label;
+        return $this->_labels[$page];
+    }
+
+    protected function _sortCandidates($a, $b)
+    {
+        if ($a->level > $b->level) {
+            return 1;
+        } else if ($a->level < $b->level) {
+            return -1;
+        } else {
+            return 0;
+        }
+    }
+
+    protected function _getVirtualPage($page)
+    {
+        $num = explode(',', $this->book->numerotation);
+        if (isset($num[$page - 1])) {
+            return $num[$page - 1];
+        }
+        return 1;
+    }
+
+    protected function escape($txt, $replaceNewLines = false)
+    {
+        $res = htmlentities($txt, ENT_COMPAT, 'UTF-8');
+        if ($replaceNewLines) {
+            $res = str_replace("\n", ' ', $res);
+            $res = str_replace("\r", '', $res);
+        }
+        return $res;
+    }
+
+    protected function makeHTMLFooter()
+    {
+        $res = '<footer>';
+        $res .= '<h2><a href="https://www.fluidbook.com">Fluidbook : Solution de catalogues interactifs et brochures en ligne</a></h2>';
+        $res .= '</footer>';
+        return $res;
+    }
+
+    protected function replaceHTML($toReplace)
+    {
+        return $this->replaceContents($this->origHTML, $toReplace);
+    }
+
+    protected function mergeJavascript()
+    {
+        $dest = WS_COMPILE_ASSETS . '/fluidbook.js';
+        $orig = WS_COMPILE_ASSETS . '/_js/';
+        $files = array('log4js.js' => false, 'esapi.js' => false, 'resources/i18n/ESAPI_Standard_en_US.properties.js' => false, 'resources/Base.esapi.properties.js' => false, 'swfobject.js' => false, 'swfaddress.js' => true, 'fluidbook.js' => true);
+
+        $refresh = false;
+        if (file_exists($dest)) {
+            $mtime = filemtime($dest);
+            foreach ($files as $file => $min) {
+                if (filemtime($orig . $file) > $mtime) {
+                    $refresh = true;
+                    break;
+                }
+            }
+        } else {
+            $refresh = true;
+        }
+        if (!$refresh) {
+            return;
+        }
+
+        $minjs = "\n\n";
+        foreach ($files as $file => $min) {
+            $c = file_get_contents($orig . $file);
+            if ($min) {
+                $c = JSMin::minify($c);
+            }
+
+            $minjs .= $c . "\n\n";
+        }
+        file_put_contents($dest, $minjs);
+    }
+
+
+}
diff --git a/app/Fluidbook/Packager/Precompiled.php b/app/Fluidbook/Packager/Precompiled.php
new file mode 100644 (file)
index 0000000..45b87e9
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Fluidbook\Packager;
+
+class Precompiled extends Online
+{
+    public function __construct($book_id, $vdir = null, $whole = true,$options=[])
+    {
+        parent::__construct($book_id, $vdir, $whole,$options);
+        $this->version = 'precompiled';
+        $this->book->parametres->embedAllLibraries = true;
+    }
+
+
+    public function prepareHTML5()
+    {
+        $res = parent::prepareHTML5();
+        $dest = $this->vdir . 'm';
+
+        // Copy styles
+        $source = wsHTML5::getSourcesPath($this->book->parametres->mobileLVersion);
+        $styles = $source . '/style';
+        $destLess = $dest . '/_less';
+        $cmd = "cp -R $styles $destLess";
+        `$cmd`;
+
+        // Copy theme assets
+        $theme = WS_THEMES . '/' . $this->book->theme . '/';
+        $destTheme = $dest . '/_theme';
+        $cmd = "cp -R -L $theme $destTheme";
+        `$cmd`;
+
+        // Cleanup
+        $clean = ['data/thumbnails', 'data/background', 'data/contents', 'data/*.pdf', 'data/style', 'style'];
+        foreach ($clean as $item) {
+            $path = $dest . '/' . $item;
+            if (strstr($item, '.')) {
+                `rm -f $path`;
+            } else {
+                `rm -rf $path`;
+            }
+        }
+
+        return $res;
+    }
+
+
+}
diff --git a/app/Fluidbook/Packager/Scorm.php b/app/Fluidbook/Packager/Scorm.php
new file mode 100644 (file)
index 0000000..dcba067
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace App\Fluidbook\Packager;
+class Scorm extends Online
+{
+    public function __construct($book_id, $vdir = null, $whole = true, $options = [])
+    {
+        parent::__construct($book_id, $vdir, $whole, $options);
+        $this->version = 'scorm';
+        $this->_disableScorm = false;
+    }
+
+    protected function preparePackage()
+    {
+        $res = parent::preparePackage();
+        $manifests = $this->vdir . '/m/data/links/*/imsmanifest.xml';
+        $cmd = "rm $manifests";
+        `$cmd`;
+        return $res;
+    }
+}
diff --git a/app/Fluidbook/Packager/Sharepoint.php b/app/Fluidbook/Packager/Sharepoint.php
new file mode 100644 (file)
index 0000000..341849a
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+
+namespace App\Fluidbook\Packager;
+
+class Sharepoint extends Online
+{
+    public function __construct($book_id, $vdir = null, $whole = true, $options = [])
+    {
+        parent::__construct($book_id, $vdir, $whole, $options);
+        $this->version = 'sharepoint';
+        $this->book->parametres->seoVersion = false;
+        $this->book->parametres->maxResolution = 150;
+        $this->book->parametres->htmlExtension = 'aspx';
+    }
+}
diff --git a/app/Fluidbook/Packager/USBKey.php b/app/Fluidbook/Packager/USBKey.php
new file mode 100644 (file)
index 0000000..e3a65c3
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+namespace App\Fluidbook\Packager;
+class USBKey extends MacOS
+{
+
+       public function __construct($book_id,$options=[])
+       {
+               parent::__construct($book_id,$options);
+               $this->version = 'win_cd_html';
+       }
+
+       protected function preparePackage()
+       {
+               parent::preparePackage();
+               $this->replaceAutorun();
+
+               // Package mac app
+               $win = ROOT . Base::package($this->book_id, 'win_inss_html', false, false);
+
+               $dest = $this->getFinalPackageDir() . "/" . $this->exeName . '.exe';
+               $cp = "cp $win $dest";
+               `$cp`;
+       }
+
+       public function replaceAutorun()
+       {
+               $inf = file_get_contents(WS_COMPILE_ASSETS . '/autorun-html.inf');
+               $toReplace = array('title' => $this->book->parametres->title, 'exe' => $this->exeName . '.exe', 'nwplatform' => $this->nwplatform);
+               $inf = $this->replaceContents($inf, $toReplace);
+               file_put_contents($this->getFinalPackageDir() . '/autorun.inf', utf8_decode($inf));
+       }
+
+
+       protected function postPackage()
+       {
+
+       }
+
+}
diff --git a/app/Fluidbook/Packager/WindowsEXE.php b/app/Fluidbook/Packager/WindowsEXE.php
new file mode 100644 (file)
index 0000000..937497d
--- /dev/null
@@ -0,0 +1,13 @@
+<?php
+namespace App\Fluidbook\Packager;
+class WindowsEXE extends WindowsInstaller
+{
+
+    protected $nsifile = 'html-silent';
+
+    public function __construct($book_id, $options = [])
+    {
+        parent::__construct($book_id, $options);
+        $this->version = 'win_inss_html';
+    }
+}
diff --git a/app/Fluidbook/Packager/WindowsInstaller.php b/app/Fluidbook/Packager/WindowsInstaller.php
new file mode 100644 (file)
index 0000000..e3c61f3
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+namespace App\Fluidbook\Packager;
+class WindowsInstaller extends WindowsZIP
+{
+
+    protected $nsi;
+    protected $nsifile = 'html';
+
+    public function __construct($book_id, $options = [])
+    {
+        parent::__construct($book_id, $options);
+        $this->version = 'win_ins_html';
+    }
+
+    protected function preparePackage()
+    {
+        parent::preparePackage();
+
+        $this->makeNSI();
+    }
+
+    protected function makeNSI()
+    {
+        global $core;
+
+        $winvdir = $this->getFinalPackageDir();
+
+        $daoLang = new wsDAOLang($core->con);
+        $lang = $daoLang->selectById($this->book->lang);
+
+        if ($lang->nsis == 'Arabic') {
+            $lang->nsis = 'English';
+        }
+
+        if (!file_exists(WS_FILES . '/packager/download')) {
+            mkdir(WS_FILES . '/packager/download', 0777, true);
+        }
+
+        $fname = $this->exeName;
+        $title = $this->appName;
+
+        $nsi = file_get_contents(WS_COMPILE_ASSETS . '/' . $this->nsifile . '.nsi');
+        $nsi = str_replace('$name', $title, $nsi);
+        $nsi = str_replace('$htmldir', WS_COMPILE_ASSETS, $nsi);
+        $nsi = str_replace('$fname', $fname, $nsi);
+        $nsi = str_replace('$fdir', $winvdir, $nsi);
+        $nsi = str_replace('$titre', $title, $nsi);
+        $nsi = str_replace('$lang', $lang->nsis, $nsi);
+        $nsi = str_replace('$nwplatform', $this->nwplatform, $nsi);
+        $nsi = str_replace('$nsisdir', '/usr/local/share/nsis', $nsi);
+        $nsi = str_replace('$output', $this->getPathBase('exe'), $nsi);
+        $favicon = $this->vdir . 'data/favicon.ico';
+        if ($this->theme->parametres->favicon == '') {
+            $this->copy(WS_COMPILE_ASSETS . '/fluidbook.ico', $favicon);
+        } else if (!file_exists($favicon)) {
+            $pngFile = WS_THEMES . '/' . $this->theme->theme_id . '/' . $this->theme->parametres->favicon;
+            $icoFile = WS_THEMES . '/' . $this->theme->theme_id . '/favicon.ico';
+            if (!file_exists($icoFile) || filemtime($icoFile) < filemtime($pngFile) || filemtime(__FILE__) > filemtime($icoFile)) {
+                $tmp = CubeIT_Files::tempnam() . '.png';
+                $convert = "convert $pngFile -resize 64x64^ -gravity center $tmp";
+                `$convert`;
+
+                $icotool = new CommandLine('icotool');
+                $icotool->setArg('c');
+                $icotool->setArg('o', $icoFile);
+                $icotool->setArg(null, $tmp);
+                $icotool->execute();
+
+                unlink($tmp);
+            }
+            $this->copy($icoFile, $favicon);
+            if (!file_exists($favicon)) {
+                $this->copy(WS_COMPILE_ASSETS . '/fluidbook.ico', $favicon);
+            }
+        }
+        $nsi = str_replace('$favicon', $favicon, $nsi);
+
+        $this->nsi = $nsi;
+    }
+
+    public function makePackage($zip)
+    {
+        $this->preparePackage();
+
+        $tmp = cubeFiles::tempnam() . '.nsi';
+        file_put_contents($tmp, $this->nsi);
+        $makensis = new CommandLine('makensis');
+        $makensis->setArg(null, '-V4');
+        $makensis->setArg(null, $tmp);
+        $makensis->execute();
+        $makensis->debug();
+        if (!file_exists($this->getPathBase('exe'))) {
+            die('Error while building the installer  : ' . $this->getPathBase('exe') . ' // ' . $makensis->commande . ' // ' . $makensis->output);
+        }
+
+        $this->signInstaller();
+
+        if (!file_exists($this->getPathBase('exe'))) {
+            die('Error during the signing process : ' . $this->getPathBase('exe'));
+        }
+
+        return $this->getURLBase('exe');
+    }
+
+    public function signInstaller()
+    {
+        $this->_sign($this->getPathBase('exe'));
+    }
+
+    public function __destruct()
+    {
+
+    }
+
+}
diff --git a/app/Fluidbook/Packager/WindowsZIP.php b/app/Fluidbook/Packager/WindowsZIP.php
new file mode 100644 (file)
index 0000000..7526a8c
--- /dev/null
@@ -0,0 +1,218 @@
+<?php
+
+namespace App\Fluidbook\Packager;
+
+use Cubist\Net\SSH2;
+use Cubist\Util\CommandLine;
+
+class WindowsZIP extends Base
+{
+    protected $exeName;
+    protected $appName;
+    protected $buildPath;
+    protected $nwplatform = 'windows-x64';
+    protected $nwversion = '0.55.0';
+    protected $appversion = '';
+    protected $node_platform = 'win';
+    protected $exenameMaxlength = 30;
+    protected $_compileOnConstruct = true;
+
+    public function __construct($book_id, $options = [])
+    {
+        parent::__construct($book_id, null, true, $options);
+        $this->version = 'win_exe_html';
+        $this->appName = '';
+        $this->appversion = '1.0.' . time();
+        $this->_clean = false;
+
+        if ($this->book->parametres->offlineTitle == "") {
+            $this->exeName = $this->book->book_id . '-' . trim(cubeText::str2URL(mb_substr($this->book->parametres->title, 0, $this->exenameMaxlength - 6)), '-');
+            $this->appName = $this->book->parametres->title;
+        } else {
+            $this->exeName = trim(cubeText::str2URL(mb_substr($this->book->parametres->offlineTitle, 0, $this->exenameMaxlength)), '-');
+            $this->appName = $this->book->parametres->offlineTitle;
+        }
+
+        if ($this->exeName === '') {
+            $this->exeName = $book_id . '-fluidbook';
+        }
+    }
+
+    protected function preparePackage()
+    {
+
+        parent::preparePackage();
+
+        $this->copyFluidbookFiles();
+        $this->makeJSON();
+
+        $this->buildPath = WS_PACKAGER . '/nwbuild/' . $this->version . '/' . $this->book_id;
+
+        `umask 0000;rm -rf $this->buildPath;mkdir -p 0777 $this->buildPath;chmod -R 777 $this->vdir;mkdir -p 0777 /application/tmp;chmod -R 777 /application/tmp`;
+
+        $cl = new CommandLine('/usr/local/web2exe/web2exe-linux');
+        $cl->setSudo(true);
+        $cl->setEnv('TMPDIR', '/application/tmp');
+        $cl->setLongArgumentSeparator(' ');
+        $cl->setArg('export-to', $this->nwplatform);
+        $cl->setArg('uncompressed-folder');
+        $cl->setArg('title', $this->appName);
+        $cl->setArg('output-dir', $this->buildPath);
+        $cl->setArg('nw-version', $this->nwversion);
+        //$cl->setArg('sdk-build');
+        $cl->setArg('main', 'index.html');
+        $cl->setArg('name', $this->exeName);
+        $cl->setArg('mac-icon', $this->vdir . 'icon.icns');
+        $cl->setArg('exe-icon', $this->vdir . 'icon.ico');
+        $cl->setArg('icon', $this->vdir . 'icon.png');
+        $cl->setArg('width', 1024);
+        $cl->setArg('height', 768);
+        $cl->setArg('app-name', $this->exeName);
+        $cl->setArg('version', $this->appversion);
+        $cl->setArg('id', 'com.fluidbook.' . $this->book_id);
+        $cl->setArg('verbose');
+        $cl->setArg(null, $this->vdir);
+        $cl->execute();
+        $cl->debug();
+
+        `sudo chown -R extranet:www-data $this->buildPath`;
+
+        if (!file_exists($this->buildPath)) {
+            die('Error while making exe : ' . $cl->commande . ' // ' . $cl->output);
+        }
+
+        $this->replaceFFMpeg();
+
+        $this->signExe();
+    }
+
+    function signExe()
+    {
+        $exe = $this->buildPath . '/' . $this->exeName . '/' . $this->nwplatform . '/' . $this->exeName . '.exe';
+        $this->_sign($exe);
+    }
+
+    function _sign($exe)
+    {
+        $rand = 'sign-' . rand(1000000, 9999999) . '.exe';
+        copy($exe, '/mnt/sshfs/codesign/' . $rand);
+        $cli = new CommandLine('C:/Program Files (x86)/Windows Kits/10/bin/10.0.18362.0/x64/signtool.exe');
+        $cli->setManualArg("sign /f C:/Users/vince/Documents/Cubedesigners.cer /csp \"eToken Base Cryptographic Provider\" /k \"[SafeNet Token JC 0{{TYWjZacq%hAH98}}]=54C3F1B91759268A\" /tr http://timestamp.sectigo.com /td sha256 /fd sha256 /a C:/Sign/$rand");
+        $cli->execute(new SSH2('paris.cubedesigners.com', 'vince', 'Y@mUC9mY2DOYWXkN', '22422'));
+        $cli->debug();
+        sleep(2);
+        copy('/mnt/sshfs/codesign/' . $rand, $exe);
+        unlink('/mnt/sshfs/codesign/' . $rand);
+    }
+
+    function replaceFFMpeg()
+    {
+        copy(WS_COMPILE_ASSETS . '/_exehtml/_ffmpeg/' . $this->nwplatform . '-ffmpeg.dll', $this->getFinalPackageDir() . '/ffmpeg.dll');
+    }
+
+    function makeJSON()
+    {
+        $data = ['app_name' => $this->appName, 'main' => 'index.html', 'name' => $this->exeName, 'version' => '1.0.' . time(),
+            'webkit' => [],
+            'window' => ['height' => 768, 'width' => 1024, 'title' => $this->appName, 'id' => 'main', 'icon' => 'icon.png', 'mac_icon' => 'icon.icns'],
+            'dependencies' => ['child_process' => "^1.0.2",
+                'fs' => '0.0.1-security',
+                'path' => '^0.12.7'],
+        ];
+
+        if ($this->book->parametres->offlineWindowsProfilePath != '') {
+            $datadir = str_replace('%title%', $this->exeName, $this->book->parametres->offlineWindowsProfilePath);
+            $data['chromium-args'] = '--crash-dumps-dir=\'' . $datadir . '\' --user-data-dir=\'' . $datadir . '\'';
+        }
+
+
+        $pngIcon = $this->vdir . '/icon.png';
+        $winIcon = $this->vdir . '/icon.ico';
+
+        $png = WS_COMPILE_ASSETS . '/fluidbook.png';
+        $ico = WS_COMPILE_ASSETS . '/fluidbook.ico';
+
+        if ($this->theme->parametres->favicon != '') {
+            if (file_exists($this->vdir . '/data/favicon.png')) {
+                $png = $this->vdir . '/data/favicon.png';
+            }
+            if (file_exists($this->vdir . '/data/favicon.ico')) {
+                $ico = $this->vdir . '/data/favicon.ico';
+            }
+        }
+
+        $icns = $this->vdir . '/icon.icns';
+        $this->copy($png, $pngIcon);
+        $this->copy($ico, $winIcon);
+        commonTools::pngToIcns($png, $icns);
+
+
+        file_put_contents($this->vdir . '/package.json', json_encode($data));
+    }
+
+    public function makePackage($zip)
+    {
+        parent::makePackage($zip);
+        $res = $this->zip();
+        $this->postPackage();
+        return $res;
+    }
+
+    public function getFinalPackageDir()
+    {
+        return $this->buildPath . '/' . $this->exeName . '/' . $this->nwplatform;
+    }
+
+    protected function compile($forceCompile = false)
+    {
+        // For exe version, force to export only the html5 version
+        // No need to export pages with texts for this version, we are certain that svg is supported if enabled
+        if ($this->book->parametres->mobileVersion == 'html5-desktop') {
+            $this->book->parametres->mobileVersion = 'html5';
+        }
+        $this->book->parametres->seoVersion = false;
+
+        $this->daoBook->compile($this->book_id, 'html5', false, $this->book->parametres->forceCompileOnDownload, false, $this->book);
+    }
+
+    protected function copyFluidbookFiles()
+    {
+        // Copie du FB vers un répertoire temporaire
+        $cp = new CommandLine('cp');
+        $cp->setArg('R');
+        $cp->setArg('p');
+        $cp->setArg(null, WS_BOOKS . '/html5/' . $this->book->book_id . '/*');
+        $cp->setArg(null, $this->vdir);
+        $cp->execute();
+
+        $this->copyExtras();
+        $this->copyNodeModules();
+    }
+
+    protected function copyExtras()
+    {
+
+        if ($this->book->parametres->form == 'bourbon') {
+
+            $dest = $this->vdir . '/exe';
+            if (!file_exists($dest)) {
+                mkdir($dest, 0777, true);
+            }
+            $this->copy(WS_FILES . '/bourbon/sendemail.exe', $dest . '/sendemail.exe');
+        }
+    }
+
+    protected function copyNodeModules()
+    {
+        $dest = $this->vdir . '/node_modules';
+        if (!file_exists($dest)) {
+            mkdir($dest, 0777, true);
+        }
+        $cp = new CommandLine('cp');
+        $cp->setArg('R');
+        $cp->setArg('p');
+        $cp->setArg(null, WS_COMPILE_ASSETS . '/_exehtml/_node_modules_' . $this->node_platform . '/*');
+        $cp->setArg(null, $dest);
+        $cp->execute();
+    }
+}
index 72a5311d2f43aa53dbb343db815afa98c599c809..cf6798be9baa1945cd906ed2bdc05f98387df43b 100644 (file)
@@ -2,9 +2,8 @@
 
 namespace App\Http\Controllers\Admin\Operations\FluidbookPublication;
 
-use App\Models\FluidbookDocument;
+use App\Fluidbook\Links;
 use App\Models\FluidbookPublication;
-use App\Util\FluidbookLinks;
 use Cubist\Backpack\Http\Controllers\Base\XSendFileController;
 use Cubist\Util\Files\Files;
 use Illuminate\Http\UploadedFile;
@@ -48,7 +47,7 @@ trait EditOperation
             abort(401);
         }
 
-        FluidbookLinks::saveLinksInFile($fluidbook_id,
+        Links::saveLinksInFile($fluidbook_id,
             backpack_user()->id,
             request('message'),
             json_decode(request('links', '[]'), true),
@@ -56,7 +55,7 @@ trait EditOperation
         );
         $fb = FluidbookPublication::find($fluidbook_id);
 
-        return response()->json(['assets' => $fb->getLinksAssetsDimensions(), 'versions' => FluidbookLinks::getLinksVersions($fluidbook_id)]);
+        return response()->json(['assets' => $fb->getLinksAssetsDimensions(), 'versions' => Links::getLinksVersions($fluidbook_id)]);
     }
 
     protected function moveLinks($fluidbook_id)
@@ -77,7 +76,7 @@ trait EditOperation
         $width = $fb->getPageWidth();
         $isOnePage = $fb->isOnePage();
 
-        FluidbookLinks::getLinksAndRulers($fluidbook_id, $links, $rulers);
+        Links::getLinksAndRulers($fluidbook_id, $links, $rulers);
 
         $rlinks = array();
         foreach ($links as $k => $link) {
@@ -125,7 +124,7 @@ trait EditOperation
             $rrulers[$k] = $ruler;
         }
 
-        FluidbookLinks::saveLinksInFile($fluidbook_id, backpack_user()->id, __('Décalage de :nb pages à partir de la page :page', ['nb' => $offset, 'page' => $from]), $rlinks, $rrulers);
+        Links::saveLinksInFile($fluidbook_id, backpack_user()->id, __('Décalage de :nb pages à partir de la page :page', ['nb' => $offset, 'page' => $from]), $rlinks, $rrulers);
         return response()->json(['success' => 'ok']);
     }
 
@@ -160,7 +159,7 @@ trait EditOperation
         if (!FluidbookPublication::hasPermission($fluidbook_id)) {
             abort(401);
         }
-        $links = FluidbookLinks::getLinksVersions($fluidbook_id);
+        $links = Links::getLinksVersions($fluidbook_id);
         return response()->json($links);
     }
 
@@ -169,8 +168,8 @@ trait EditOperation
         if (!FluidbookPublication::hasPermission($fluidbook_id)) {
             abort(401);
         }
-        FluidbookLinks::getLinksAndRulers($fluidbook_id, $links, $rulers, $version);
-        $xlsx = FluidbookLinks::linksToExcel($links, $rulers);
+        Links::getLinksAndRulers($fluidbook_id, $links, $rulers, $version);
+        $xlsx = Links::linksToExcel($links, $rulers);
         $tmpfile = Files::tempnam() . '.xlsx';
         $writer = new Xlsx($xlsx);
         $writer->save($tmpfile);
@@ -183,8 +182,8 @@ trait EditOperation
             abort(401);
         }
 
-        FluidbookLinks::getLinksAndRulers($fluidbook_id, $links, $rulers, $version);
-        FluidbookLinks::saveLinksInFile($fluidbook_id, backpack_user()->id, __('Restaurer la sauvegarde des liens :date', ['date' => date('Y-m-d H:i:s', $version)]), $links, $rulers, [], []);
+        Links::getLinksAndRulers($fluidbook_id, $links, $rulers, $version);
+        Links::saveLinksInFile($fluidbook_id, backpack_user()->id, __('Restaurer la sauvegarde des liens :date', ['date' => date('Y-m-d H:i:s', $version)]), $links, $rulers, [], []);
         return response()->json(['success' => 'ok']);
     }
 
@@ -196,8 +195,8 @@ trait EditOperation
         /** @var UploadedFile $uploadedFile */
         $uploadedFile = request()->file('file');
 
-        FluidbookLinks::getLinksAndRulersFromExcelFile($uploadedFile->getPathname(), $links, $rulers);
-        FluidbookLinks::saveLinksInFile($fluidbook_id, backpack_user()->id, __("Remplacer les liens à partir du fichier :file", ['file' => $uploadedFile->getClientOriginalName()]), $links, $rulers, [], []);
+        Links::getLinksAndRulersFromExcelFile($uploadedFile->getPathname(), $links, $rulers);
+        Links::saveLinksInFile($fluidbook_id, backpack_user()->id, __("Remplacer les liens à partir du fichier :file", ['file' => $uploadedFile->getClientOriginalName()]), $links, $rulers, [], []);
         return response()->json(['success' => 'ok']);
     }
 
@@ -210,8 +209,8 @@ trait EditOperation
         /** @var UploadedFile $uploadedFile */
         $uploadedFile = request()->file('file');
 
-        FluidbookLinks::getLinksAndRulers($fluidbook_id, $merged_links, $merged_rulers);
-        FluidbookLinks::getLinksAndRulersFromExcelFile($uploadedFile->getPathname(), $links, $rulers);
+        Links::getLinksAndRulers($fluidbook_id, $merged_links, $merged_rulers);
+        Links::getLinksAndRulersFromExcelFile($uploadedFile->getPathname(), $links, $rulers);
         $existing_uids = [];
         foreach ($merged_links as $merged_link) {
             $existing_uids[$merged_link['uid']] = true;
@@ -219,7 +218,7 @@ trait EditOperation
 
         foreach ($links as $link) {
             if (isset($existing_uids[$link['uid']])) {
-                $link['uid'] = FluidbookLinks::generateUID();
+                $link['uid'] = Links::generateUID();
                 $existing_uids[$link['uid']] = true;
             }
             $merged_links[] = $link;
@@ -227,7 +226,7 @@ trait EditOperation
 
         $merged_rulers = array_merge($merged_rulers, $rulers);
 
-        FluidbookLinks::saveLinksInFile($fluidbook_id, backpack_user()->id, __("Ajouter les liens à partir du fichier :file", ['file' => $uploadedFile->getClientOriginalName()]) . ' ', $merged_links, $merged_rulers, [], []);
+        Links::saveLinksInFile($fluidbook_id, backpack_user()->id, __("Ajouter les liens à partir du fichier :file", ['file' => $uploadedFile->getClientOriginalName()]) . ' ', $merged_links, $merged_rulers, [], []);
         return response()->json(['success' => 'ok']);
     }
 
index 49172d88febc8a1c3b50596001b1e1cecf08e6cc..f2f97568f600ee6e07419dbf7b4084c441f4eabf 100644 (file)
@@ -2,17 +2,18 @@
 
 namespace App\Jobs;
 
+use App\Fluidbook\Links;
+use App\Fluidbook\Link\Link;
 use App\Fluidbook\PDF;
-use App\Http\Controllers\Admin\Operations\FluidbookPublication\Services\SocialImageOperation;
-use App\Http\Controllers\Admin\Operations\Tools\Favicon;
-use App\Models\Signature;
 use App\Fluidbook\SearchIndex;
 use App\Fluidbook\SEO\Document;
+use App\Http\Controllers\Admin\Operations\FluidbookPublication\Services\SocialImageOperation;
+use App\Http\Controllers\Admin\Operations\Tools\Favicon;
 use App\Models\FluidbookPublication;
 use App\Models\FluidbookTheme;
 use App\Models\FluidbookTranslate;
+use App\Models\Signature;
 use App\Models\Traits\FluidbookPlayerBranches;
-use App\Util\FluidbookLinks;
 use Cubist\Backpack\Magic\Fields\Checkbox;
 use Cubist\Excel\ExcelToArray;
 use Cubist\Locale\Country;
@@ -33,11 +34,9 @@ use Cubist\Util\WebVideo;
 use DOMDocument;
 use DOMElement;
 use DOMXPath;
-use Faker\Provider\File;
 use Fluidbook\Tools\Compiler\CompilerInterface;
 use Fluidbook\Tools\Links\AnchorLink;
 use Fluidbook\Tools\Links\ContentLink;
-use App\Fluidbook\Link\Link;
 use Fluidbook\Tools\SVG\SVGTools;
 use Illuminate\Console\Command;
 use SimpleXMLElement;
@@ -662,7 +661,7 @@ class FluidbookCompiler extends Base implements CompilerInterface
         $file = $cdir . $this->fluidbookSettings->basketReferences;
         $this->config->basketReferences = ExcelToArray::excelToArrayKeyVars($file);
 
-        FluidbookLinks::getLinksAndRulers($this->book_id, $links, $rulers);
+        Links::getLinksAndRulers($this->book_id, $links, $rulers);
 
         foreach ($links as $link) {
             if ($link['type'] == '12') {
@@ -693,7 +692,7 @@ class FluidbookCompiler extends Base implements CompilerInterface
         $file = $cdir . $this->fluidbookSettings->basketReferences;
         $this->config->basketReferences = ExcelToArray::excelToArrayKeyVars($file);
 
-        FluidbookLinks::getLinksAndRulers($this->book_id, $links, $rulers);
+        Links::getLinksAndRulers($this->book_id, $links, $rulers);
 
         foreach ($this->config->basketReferences as $ref => $data) {
             $source = $cdir . '/' . $data['Image'];
@@ -775,7 +774,7 @@ class FluidbookCompiler extends Base implements CompilerInterface
             $this->vdir->copy($cdir . $f, 'data/commerce/' . $f);
         }
 
-        FluidbookLinks::getLinksAndRulers($this->book_id, $links, $rulers);
+        Links::getLinksAndRulers($this->book_id, $links, $rulers);
     }
 
     public function writeGrandPavoisCart()
@@ -795,7 +794,7 @@ class FluidbookCompiler extends Base implements CompilerInterface
         $file = $cdir . $this->fluidbookSettings->basketReferences;
         $this->config->basketReferences = ExcelToArray::excelToArrayKeyVars($file);
 
-        FluidbookLinks::getLinksAndRulers($this->book_id, $links, $rulers);
+        Links::getLinksAndRulers($this->book_id, $links, $rulers);
     }
 
 
@@ -816,7 +815,7 @@ class FluidbookCompiler extends Base implements CompilerInterface
             $this->config->eanReferences = ExcelToArray::excelToArrayIndexKeyVars($eanFile);
         }
 
-        FluidbookLinks::getLinksAndRulers($this->book_id, $links, $rulers);
+        Links::getLinksAndRulers($this->book_id, $links, $rulers);
         foreach ($links as $link) {
             if ($link['type'] == '12' && isset($this->config->basketReferences[$link['to']])) {
                 $this->config->basketReferences[$link['to']]['zoom_image'] = 'data/links/zoom_' . $link['uid'] . '.jpg';
@@ -2187,7 +2186,7 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
             $this->vdir->copyDirectory($d['dir'], $d['fdir']);
         }
 
-        FluidbookLinks::getLinksAndRulers($this->book_id, $links, $rulers);
+        Links::getLinksAndRulers($this->book_id, $links, $rulers);
 
         if ($this->fluidbookSettings->basketManager === 'Puma') {
             foreach ($links as $k => $init) {
@@ -2283,7 +2282,7 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
                 $anchorExists[$linkData['to']] = $linkData;
             }
             if ($linkData['type'] == 35 || $linkData['type'] == 15 || $linkData['type'] == 39) {
-                $linkData = FluidbookLinks::decryptLink($linkData);
+                $linkData = Links::decryptLink($linkData);
                 $animations = ContentLink::parseAnimations($linkData['image_rollover']);
                 foreach ($animations as $animation) {
                     if (isset($animation['backgroundcolor']) && $animation['backgroundcolor'] !== 'transparent') {
index 3ae4385688d3a3142f9a20811f19f185a4a0e9de..30a4566b630d97d84f762ba1b6ffa686cef9dd9e 100644 (file)
@@ -2,10 +2,10 @@
 
 namespace App\Models;
 
+use App\Fluidbook\Farm;
 use App\Jobs\FluidbookDocumentFileProcess;
 use App\Jobs\FluidbookDocumentUpload;
 use App\Models\Base\ToolboxModel;
-use App\Util\FluidbookFarm;
 use Cubist\Backpack\Magic\Fields\Integer;
 use Cubist\Backpack\Magic\Fields\Text;
 use Cubist\Backpack\Magic\Fields\Textarea;
@@ -329,7 +329,7 @@ class FluidbookDocument extends ToolboxModel
     public function _getFile($page, $format = 'jpg', $resolution = 150, $withText = true, $withGraphics = true, $version = 'html')
     {
         if (!$this->hasFile($page, $format, $resolution, $withText, $withGraphics, $version)) {
-            return FluidbookFarm::getFile($page, $format, $resolution, $withText, $withGraphics, $version, $this->getResolutionRatio(), $this->getMobileFirstRatio(), $this->path());
+            return Farm::getFile($page, $format, $resolution, $withText, $withGraphics, $version, $this->getResolutionRatio(), $this->getMobileFirstRatio(), $this->path());
         }
 
         $path = $this->_getPath($page, $format, $resolution, $withText, $withGraphics, $version);
index 05d57a9baf80e9d39afeec55bb06c632bab5ebca..41c57325d3fff6d2475ac71678d1c79ce31fb868 100644 (file)
@@ -8,17 +8,17 @@ use App\Fields\FluidbookChapters;
 use App\Fields\FluidbookComposition;
 use App\Fields\FluidbookLocale;
 use App\Fields\User;
+use App\Fluidbook\Links;
 use App\Http\Controllers\Admin\Operations\FluidbookPublication\CompositionOperation;
 use App\Http\Controllers\Admin\Operations\FluidbookPublication\DeletefbOperation;
 use App\Http\Controllers\Admin\Operations\FluidbookPublication\DownloadOperation;
+use App\Http\Controllers\Admin\Operations\FluidbookPublication\EditOperation;
 use App\Http\Controllers\Admin\Operations\FluidbookPublication\PreviewOperation;
 use App\Http\Controllers\Admin\Operations\FluidbookPublication\StatsOperation;
-use App\Http\Controllers\Admin\Operations\FluidbookPublication\EditOperation;
 use App\Jobs\FluidbookImagesPreprocess;
 use App\Models\Base\ToolboxSettingsModel;
 use App\Models\Traits\PublicationSettings;
 use App\Models\Traits\SCORMVersionTrait;
-use App\Util\FluidbookLinks;
 use Backpack\CRUD\app\Http\Controllers\Operations\CloneOperation;
 use Backpack\CRUD\app\Library\Widget;
 use Cubist\Backpack\Magic\Fields\FormBigSection;
@@ -373,7 +373,7 @@ class FluidbookPublication extends ToolboxSettingsModel
 
     public function getLinksAndRulers(&$links, &$rulers)
     {
-        FluidbookLinks::getLinksAndRulers($this->id, $links, $rulers);
+        Links::getLinksAndRulers($this->id, $links, $rulers);
     }
 
     public function getLinksAssetsDimensions()
diff --git a/app/Util/FluidbookFarm.php b/app/Util/FluidbookFarm.php
deleted file mode 100644 (file)
index ebfd5d4..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-<?php
-
-namespace App\Util;
-
-use Cubist\Util\Files\Files;
-use hollodotme\FastCGI\Client;
-use hollodotme\FastCGI\Requests\PostRequest;
-use hollodotme\FastCGI\SocketConnections\NetworkSocket;
-
-class FluidbookFarm
-{
-    protected static $_farmServers = [
-        ['name' => 'alphaville', 'host' => 'fluidbook-processfarm', 'port' => 9000, 'weight' => 24],
-        ['name' => 'brazil', 'host' => 'brazil.cubedesigners.com', 'weight' => 6],
-        ['name' => 'clockwork', 'host' => 'clockwork.cubedesigners.com', 'weight' => 2],
-        ['name' => 'dracula', 'host' => 'dracula.cubedesigners.com', 'weight' => 3],
-        ['name' => 'elephantman', 'host' => 'elephantman.cubedesigners.com', 'weight' => 1],
-        ['name' => 'fastandfurious', 'host' => 'fastandfurious.cubedesigners.com', 'weight' => 1],
-        ['name' => 'godzilla', 'host' => 'godzilla.cubedesigners.com', 'weight' => 3],
-        ['name' => 'her', 'host' => 'her2.cubedesigners.com', 'weight' => 4],
-        ['name' => 'isleofdogs', 'host' => 'paris.cubedesigners.com', 'port' => 9458, 'weight' => 2],
-        ['name' => 'jumanji', 'host' => 'paris.cubedesigners.com', 'port' => 9459, 'weight' => 2],
-    ];
-
-    protected static function _pingCache()
-    {
-        return Files::mkdir(storage_path('fluidbookfarm')) . '/pings';
-    }
-
-    protected static function _serversCache()
-    {
-        return Files::mkdir(storage_path('fluidbookfarm')) . '/servers';
-    }
-
-    public static function getServers()
-    {
-        return self::$_farmServers;
-    }
-
-    public static function pickOneServer()
-    {
-        $hat = [];
-        $pingCache = self::_pingCache();
-        if (!file_exists($pingCache)) {
-            self::ping(false);
-        }
-        $pings = json_decode(file_get_contents(self::_pingCache()));
-
-        foreach (self::$_farmServers as $k => $farmServer) {
-            if (!isset($pings[$k]) || !$pings[$k]) {
-                continue;
-            }
-            for ($i = 0; $i < $farmServer['weight']; $i++) {
-                $hat[] = $k;
-            }
-        }
-        shuffle($hat);
-        $i = array_pop($hat);
-        return self::$_farmServers[$i];
-    }
-
-    public static function getFCGIConnexion(array $farm, $timeout = 240): NetworkSocket
-    {
-        $timeout *= 1000;
-        return new NetworkSocket($farm['host'], $farm['port'] ?? 9457, $timeout, $timeout);
-    }
-
-    public static function sendRequest($farmer, $url, $params = [], $timeout = 240)
-    {
-        set_time_limit(0);
-        $client = new Client();
-        $response = $client->sendRequest(self::getFCGIConnexion($farmer, $timeout), new PostRequest($url, http_build_query($params)));
-        return trim($response->getBody());
-    }
-
-    public static function getFile($page, $format, $resolution, $withText, $withGraphics, $version, $resolutionRatio, $mobileFirstRatio, $path, $force = false)
-    {
-        $start = microtime(true);
-        $farmer = self::pickOneServer();
-
-        $params = ['page' => $page, 'format' => $format, 'resolution' => $resolution, 'withText' => $withText, 'withGraphics' => $withGraphics, 'version' => $version, 'force' => $force, 'out' => $path, 'resolutionRatio' => $resolutionRatio, 'mobileRatio' => $mobileFirstRatio];
-
-        $output = self::sendRequest($farmer, 'process.php', $params);
-        if (preg_match('|/data1/extranet/www/[^\s]+|', $output, $matches)) {
-            $o = $matches[0];
-        } else {
-            $o = $output;
-        }
-
-        if (file_exists($o)) {
-            $res = $o;
-        } else {
-            echo $o;
-            $res = false;
-        }
-
-        $time = round(microtime(true) - $start, 4);
-        $log = '[' . $farmer['name'] . ']' . "\t" . date('Y-m-d H:i:s') . "\t" . $time . "\t$page|$format|$resolution|$withText|$withGraphics|$version\t$res\t" . $output . "\n";
-
-        error_log($log);
-
-        return $res;
-    }
-
-    public static function ping($echo = true, $force = false)
-    {
-        $cache = self::_pingCache();
-        $servers = self::getServers();
-        $pings = [];
-        if (file_exists($cache)) {
-            $cached = json_decode(file_get_contents($cache));
-            if (is_countable($cached) && count($cached) === count($servers)) {
-                $pings = $cached;
-            }
-        }
-
-        foreach ($servers as $id => $farmer) {
-            if ($echo) {
-                echo $farmer['name'] . ' (' . $id . ') || ';
-            }
-            if (isset($pings[$id]) && !$pings[$id]) {
-                // If ping failed recently, we wait a bit before trying again.
-                if (!$force && rand(0, 9) != 5) {
-                    if ($echo) {
-                        echo 'Skipped, will try again soon' . "\n";
-                    }
-                    continue;
-                }
-            }
-            try {
-                $res = self::sendRequest($farmer, 'ping.php', [], 5);
-                $ok = $res == '1';
-            } catch (\Exception $e) {
-                $res = $e->getMessage();
-                $ok = false;
-            }
-
-            if ($echo) {
-                echo ($ok ? 'OK' : 'KO') . ' : ' . trim($res) . "\n";
-            }
-
-            $pings[$id] = $ok;
-        }
-        file_put_contents($cache, json_encode($pings));
-        file_put_contents(self::_serversCache(), json_encode($servers));
-    }
-}
diff --git a/app/Util/FluidbookLinks.php b/app/Util/FluidbookLinks.php
deleted file mode 100644 (file)
index cf6cc28..0000000
+++ /dev/null
@@ -1,478 +0,0 @@
-<?php
-
-namespace App\Util;
-
-use App\Models\FluidbookPublication;
-use App\Models\User;
-use Cubist\Util\ArrayUtil;
-use Cubist\Util\Crypt;
-use Cubist\Util\Files\Files;
-use Cubist\Util\ObjectUtil;
-use Cubist\Util\Str;
-use Fluidbook\Tools\Links\Link;
-use PhpOffice\PhpSpreadsheet\Cell\DataType;
-use PhpOffice\PhpSpreadsheet\Exception;
-use PhpOffice\PhpSpreadsheet\Spreadsheet;
-use PhpOffice\PhpSpreadsheet\Style\Alignment;
-use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
-use SodiumException;
-
-class FluidbookLinks
-{
-    protected static $_testLinkCache = null;
-    protected static $_linksKey = null;
-
-    /**
-     * @throws Exception
-     */
-    public static function linksToExcel($links, $rulers, $pages = null)
-    {
-        set_time_limit(0);
-
-        $cols = array(
-            'uid' => __('Identifiant unique'),
-            'page' => __('Page de la publication'), 'left' => __('x'), 'top' => __('y'), 'width' => __('Largeur'), 'height' => __('Hauteur'), 'rot' => __('Rotation'),
-            'type' => __('Type'), 'to' => __('Destination'), 'target' => __('Cible'),
-            'infobulle' => __('Infobulle'), 'numerotation' => __('Numérotation'),
-            'display_area' => __('Activer la surbrillance'),
-            'video_loop' => __('Video : boucle'), 'video_auto_start' => __('Video : démarrage automatique'), 'video_controls' => __('Vidéo : afficher les contrôles'), 'video_sound_on' => __('Vidéo : activer le son'),
-            'inline' => __('Vidéo : afficher dans la page'), 'video_width' => __('Vidéo : Largeur du popup'), 'video_height' => __('Vidéo : Hauteur du popup'),
-            'interactive' => __('Interactivité'), 'video_service' => __('Webvideo : service'),
-            'extra' => __('Paramètre supplémentaire'),
-            'alternative' => __('Alternative'),
-            'read_mode' => __('Mode de lecture'),
-            'image' => __('Image'), 'image_rollover' => __('Animation au survol'),
-            'animation' => __('Animation'),
-            'group' => __('Groupe'),
-            'zindex' => __('Profondeur'),
-        );
-
-        $comments = array();
-
-        $xls = new Spreadsheet();
-        $s = $xls->setActiveSheetIndex(0);
-        $s->setTitle('Links');
-
-        // Labels
-        $i = 1;
-        foreach ($cols as $id => $label) {
-            $s->setCellValueByColumnAndRow($i, 1, $id);
-            $s->getColumnDimensionByColumn($i)->setAutoSize(true);
-            $s->getStyleByColumnAndRow($i, 1)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
-            $i++;
-        }
-
-        // Links
-        self::_correctImageSpecialLinks($links);
-        $j = 2;
-        foreach ($links as $l) {
-            $i = 1;
-            foreach ($cols as $id => $label) {
-                if (($id == 'document_id' || $id == 'document_page')) {
-                    if (!is_null($pages)) {
-                        $infos = $pages[$l['page']];
-                        $value = $infos[$id];
-                    } else {
-                        $value = '';
-                    }
-                } else {
-
-                    if (isset($l[$id])) {
-                        if (is_bool($l[$id])) {
-                            $l[$id] = $l[$id] ? '1' : '0';
-                        }
-                        if ($id === 'numerotation') {
-                            if ($l[$id] === 'false') {
-                                $l[$id] = 'physical';
-                            }
-                        }
-                        if ($id === 'to') {
-                            $s->getCellByColumnAndRow($i, $j)->setDataType(DataType::TYPE_STRING)->getStyle()->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_TEXT);
-                        }
-                        $value = $l[$id];
-                    } else {
-                        $value = '';
-                    }
-                }
-
-                $s->setCellValueExplicitByColumnAndRow($i, $j, $value, DataType::TYPE_STRING);
-                $i++;
-            }
-            $j++;
-        }
-        // Rulers
-        $s = $xls->createSheet();
-        $s->setTitle('Rulers');
-
-        $rcols = array('page', 'type', 'pos');
-        $i = 1;
-        // Labels
-        foreach ($rcols as $id) {
-            $s->setCellValueByColumnAndRow($i, 1, $id);
-            $s->getColumnDimensionByColumn($i)->setAutoSize(true);
-            $i++;
-        }
-
-        // Contents
-        $j = 2;
-        foreach ($rulers as $r) {
-            $i = 1;
-            foreach ($rcols as $id) {
-                if (!is_null($pages) && ($id == 'document_id' || $id == 'document_page')) {
-                    $infos = $pages[$r['page']];
-                    $value = $infos[$id];
-                } else {
-                    $value = $r[$id];
-                }
-                $s->setCellValueByColumnAndRow($i, $j, $value);
-                $s->getStyleByColumnAndRow($i, $j)->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT);
-                $i++;
-            }
-            $j++;
-        }
-
-        $xls->setActiveSheetIndex(0);
-        return $xls;
-    }
-
-
-    public static function getLinksAndRulers($book_id, &$links, &$rulers, $time = 'latest')
-    {
-        if (null === $time) {
-            $time = 'latest';
-        }
-        $dir = self::getLinksDir($book_id);
-
-        $file = $dir . '/' . $time . '.links3.gz';
-        if ($time === 'latest' && !file_exists($file)) {
-            $versions = self::getLinksVersions($book_id);
-            foreach ($versions as $version => $m) {
-                copy(Files::firstThatExists($dir . '/' . $version . '.links3.gz', $dir . '/' . $version . '.links.gz'), $dir . '/latest.links3.gz');
-                copy(Files::firstThatExists($dir . '/' . $version . '.meta3.gz', $dir . '/' . $version . '.meta.gz'), $dir . '/latest.meta3.gz');
-                break;
-            }
-        }
-        if (!file_exists($file)) {
-            $links = [];
-            $rulers = [];
-            return;
-        }
-
-        $r = json_decode(gzdecode(file_get_contents($file)), true);
-        $links = self::_UID($r['links']);
-        $rulers = self::_UID($r['rulers']);
-        if (can('fluidbook-publication:links:edit-animations')) {
-            $links = Link::decryptLinks($links);
-        }else{
-            $links = Link::encryptLinks($links);
-        }
-
-        self::_correctImageSpecialLinks($links);
-    }
-
-    protected static function _UID($items)
-    {
-        $res = [];
-        foreach ($items as $item) {
-            if (!isset($item['uid'])) {
-                $item['uid'] = self::uid();
-            }
-            $res[$item['uid']] = $item;
-        }
-        return $res;
-    }
-
-    protected static function uid()
-    {
-        return Str::lower(Str::random(12));
-    }
-
-    protected static function _correctImageSpecialLinks(&$links)
-    {
-        foreach ($links as $k => $link) {
-            if (preg_match('/^link_(.*)$/', $link['page'], $matches) && strlen($matches[1]) !== 32) {
-                $uid = $matches[1];
-                foreach ($links as $l) {
-                    if ($l['uid'] === $uid && $l['alternative']) {
-                        $links[$k]['page'] = 'link_' . md5($l['alternative']);
-                        break;
-                    }
-                }
-            } else if (preg_match('/^([0-9a-f]{32})$/', $link['page'], $matches)) {
-                $links[$k]['page'] = 'link_' . $matches[1];
-            }
-        }
-    }
-
-    public static function getLinksFromExcel($xls, &$links, &$rulers)
-    {
-        $s = $xls->setActiveSheetIndexByName('Links');
-        $i = 0;
-        $links = array();
-        foreach ($s->getRowIterator() as $row) {
-            $cellIterator = $row->getCellIterator();
-            $cellIterator->setIterateOnlyExistingCells(false);
-            if ($i == 0) {
-                $cols = array();
-                foreach ($cellIterator as $cell) {
-                    $cols[] = $cell->getValue();
-                }
-            } else {
-                $link = array();
-                $j = 0;
-                foreach ($cellIterator as $cell) {
-                    $link[$cols[$j]] = $cell->getValue();
-                    $j++;
-                }
-                if ($link['display_area'] == '' || !$link['display_area']) {
-                    $link['display_area'] = '0';
-                }
-                if (trim($link['infobulle']) == '') {
-                    $link['infobulle'] = '';
-                }
-                $links[] = $link;
-            }
-
-            $i++;
-        }
-
-        $i = 0;
-        $rulers = array();
-        $s = $xls->setActiveSheetIndexByName('Rulers');
-        foreach ($s->getRowIterator() as $row) {
-            $cellIterator = $row->getCellIterator();
-            $cellIterator->setIterateOnlyExistingCells(false);
-            if ($i == 0) {
-                $cols = array();
-                foreach ($cellIterator as $cell) {
-                    $cols[] = $cell->getValue();
-                }
-            } else {
-                $link = array();
-                $j = 0;
-                foreach ($cellIterator as $cell) {
-                    $ruler[$cols[$j]] = $cell->getValue();
-                    $j++;
-                }
-
-                $rulers[] = $ruler;
-            }
-            $i++;
-        }
-
-        self::_correctImageSpecialLinks($links);
-    }
-
-    public static function getLinksFromAutobookmarkText($txt, &$links, &$rulers)
-    {
-        $links = array();
-        $rulers = array();
-
-        $lines = explode("\n", $txt);
-
-        $protocols = array('mailto' => 3, 'custom' => 7, 'cart' => 12, 'pagelabel' => 26);
-
-        foreach ($lines as $line) {
-            $line = trim($line);
-            if ($line == '') {
-                continue;
-            }
-            if (strpos('#', $line) === 0) {
-                continue;
-            }
-            $target = $numerotation = '';
-            list($page, $left, $top, $width, $height, $type, $to) = explode(';', $line);
-            if ($type <= 2) {
-                $target = '_blank';
-            } elseif ($type == 5) {
-                $numerotation = 'physical';
-            }
-
-            $links[] = array(
-                'page' => $page,
-                'left' => $left, 'top' => $top, 'width' => $width, 'height' => $height, 'rot' => '',
-                'type' => $type, 'to' => $to, 'target' => $target,
-                'infobulle' => '', 'numerotation' => $numerotation, 'display_area' => '1');
-        }
-
-        self::_correctImageSpecialLinks($links);
-    }
-
-    public static function saveLinksInFile($book_id, $user_id, $comments, $links, $rulers = [], $specialLinks = [], $specialRulers = [])
-    {
-        $lr = self::mergeLinksAndRulers($links, $rulers, $specialLinks, $specialRulers);
-        $meta = ['links' => count($lr['links']), 'rulers' => count($lr['rulers']), 'comments' => $comments, 'user' => $user_id];
-        $base = self::getLinksDir($book_id) . '/' . time();
-        $latestLinks = self::getLinksDir($book_id) . '/latest.links3.gz';
-        $latestMeta = self::getLinksDir($book_id) . '/latest.meta3.gz';
-        file_put_contents($base . '.meta3.gz', gzencode(json_encode($meta)));
-        file_put_contents($base . '.links3.gz', gzencode(json_encode($lr)));
-        copy($base . '.links3.gz', $latestLinks);
-        copy($base . '.meta3.gz', $latestMeta);
-    }
-
-
-    public static function getLinksDir($book_id)
-    {
-        return Files::mkdir('/data/extranet/www/fluidbook/books/links/' . $book_id);
-    }
-
-    public static function getLinksVersions($book_id)
-    {
-        $dir = self::getLinksDir($book_id);
-        $dr = opendir($dir);
-        $updates = [];
-        while ($f = readdir($dr)) {
-            if ($f === '.' || $f === '..') {
-                continue;
-            }
-            $e = explode('.', $f, 2);
-            if (($e[1] !== 'meta.gz' && $e[1] !== 'meta3.gz') || $e[0] === 'latest') {
-                continue;
-            }
-
-            $updates[$e[0]] = self::getMeta($book_id, $e[0]);
-        }
-        krsort($updates);
-
-
-        $res = [];
-        foreach ($updates as $timestamp => $u) {
-            try {
-                $u['name'] = User::find($u['user'])->name;
-            } catch (\Exception $e) {
-                $u['name'] = '-';
-            }
-            $u['date'] = date('Y-m-d H:i:s', $timestamp);
-            $u['timestamp'] = $timestamp;
-            $res[] = $u;
-        }
-
-        return $res;
-    }
-
-    public static function getMeta($book_id, $update = 'latest')
-    {
-        return json_decode(gzdecode(file_get_contents(Files::firstThatExists(self::getLinksDir($book_id) . '/' . $update . '.meta3.gz', self::getLinksDir($book_id) . '/' . $update . '.meta.gz'))), true);
-    }
-
-    public static function mergeLinksAndRulers($links, $rulers, $specialLinks, $specialRulers)
-    {
-        $finalLinks = [];
-        $l = array_merge(self::_getAsArray($links), self::_getAsArray($specialLinks));
-
-        $k = 0;
-        foreach ($l as $item) {
-            $item['id'] = $k + 1;
-            if (!isset($item['to'])) {
-                $item['to'] = '';
-            }
-            $finalLinks[] = $item;
-            $k++;
-        }
-
-        self::_correctImageSpecialLinks($finalLinks);
-
-        return ['links' => Link::encryptLinks($finalLinks), 'rulers' => array_merge(self::_getAsArray($rulers), self::_getAsArray($specialRulers))];
-    }
-
-    protected static function _getAsArray($v)
-    {
-        if (is_array($v)) {
-            return $v;
-        }
-        return json_decode($v, true);
-    }
-
-    public static function addLinksFromPDF($book_id)
-    {
-        global $core;
-
-        $daoBook = new wsDAOBook($core->con);
-        $pages = $daoBook->getPagesOfBook($book_id);
-
-        $booleans = array('video_loop', 'video_auto_start', 'video_controls', 'video_sound_on');
-        $numbers = ['left', 'top', 'width', 'height'];
-
-        $links = [];
-
-        foreach ($pages as $page => $info) {
-            $csv = wsDocument::getDir($info['document_id']) . '/p' . $info['document_page'] . '.csv';
-            if (!file_exists($csv) && file_exists($csv . '.gz')) {
-                $csv = 'compress.zlib://' . $csv . '.gz';
-            } elseif (!file_exists($csv)) {
-                continue;
-            }
-
-            $newformat = (filemtime($csv) > 1363685416);
-
-            $fp = fopen($csv, 'rb');
-
-            while (true) {
-                $line = fgetcsv($fp, 512, ';', '"');
-                // End of file
-                if (!$line) {
-                    break;
-                }
-
-                // Commentaire || ligne vide
-                if (substr($line[0], 0, 1) == '#' || is_null($line[0])) {
-                    continue;
-                }
-
-                $link = [];
-                if ($newformat) {
-                    $cols = array('page' => '', 'left' => '', 'top' => '', 'width' => '', 'height' => '', 'type' => '', 'to' => '', 'target' => '_blank', 'video_loop' => true, 'video_auto_start' => true, 'video_controls' => true, 'video_sound_on' => true, 'infobulle' => '', 'numerotation' => 'physical', "inline" => true);
-                } else {
-                    $cols = array('page' => '', 'type' => '', 'to' => '', 'left' => '', 'top' => '', 'width' => '', 'height' => '', 'target' => '_blank', 'video_loop' => true, 'video_auto_start' => true, 'video_controls' => true, 'video_sound_on' => true, 'infobulle' => '', 'numerotation' => 'physical');
-                }
-
-
-                $k = 0;
-                foreach ($cols as $col => $default) {
-                    if (isset($line[$k])) {
-                        if (in_array($k, $numbers)) {
-                            $link[$col] = (float)str_replace(',', '.', $line[$k]);
-                        } else if (in_array($k, $booleans)) {
-                            $link[$col] = ($line[$k] == '1');
-                        } else {
-                            $link[$col] = utf8_encode($line[$k]);
-                        }
-                    } else {
-                        $link[$col] = $default;
-                    }
-                    $k++;
-                }
-
-                if ($link['type'] == 18) {
-                    $link['infobulle'] = $link['to'];
-                    $link['to'] = '';
-                }
-
-                $link['display_area'] = '1';
-                $link['page'] = $page;
-                $links[] = $link;
-            }
-
-        }
-
-        self::saveLinksInFile($book_id, $core->user->utilisateur_id, 'Links imported from PDF', $links, []);
-    }
-
-    public static function getLinksAndRulersFromExcelFile($path, &$links, &$rulers)
-    {
-        $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
-        $xls = $reader->load($path);
-        FluidbookLinks::getLinksFromExcel($xls, $links, $rulers);
-    }
-
-    public static function generateUID()
-    {
-        $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
-        $randstring = '';
-        for ($i = 0; $i < 12; $i++) {
-            $randstring = $characters[rand(0, 35)];
-        }
-        return $randstring;
-    }
-}