routes profile
authorArno Renevier <arno@renevier.net>
Wed, 28 Dec 2011 17:18:24 +0000 (18:18 +0100)
committerArno Renevier <arno@renevier.net>
Fri, 17 Feb 2012 07:54:58 +0000 (08:54 +0100)
19 files changed:
.gitmodules
COPYING.TXT
application/Bootstrap.php
application/configs/application.ini
application/controllers/ErrorController.php
application/controllers/IdxController.php
application/controllers/ProfileController.php [new file with mode: 0644]
application/controllers/helpers/SyjAltiService.php [new file with mode: 0644]
application/controllers/helpers/SyjReset.php
application/controllers/plugins/SyjLoggedUser.php
application/exception/NotImplemented.php [new file with mode: 0644]
application/models/Path.php
application/models/PathMapper.php
application/resources/DejaVuSans.ttf [new file with mode: 0644]
application/views/helpers/FullBaseUrl.php [new file with mode: 0644]
application/views/scripts/idx/index.phtml
library/alti.php [new symlink]
library/altiphp [new submodule]
public/js/syj.js

index dea91ea..e2e26fa 100644 (file)
@@ -13,3 +13,6 @@
 [submodule "public/store"]
        path = public/store
        url = git://github.com/cloudhead/store.js.git
+[submodule "library/altiphp"]
+       path = library/altiphp
+       url = https://github.com/arenevier/altiphp.git
index e83ada2..56f5a14 100644 (file)
@@ -14,6 +14,8 @@
   layer-switcher-maximize.png from OpenLayers. As such it is licensed under a
   modified BSD license.
 
+- DejaVuSans.ttf is part of dejavu and is public domain.
+
 
 ===============================================================================
 
index f3fb399..0d0d4cf 100644 (file)
@@ -3,7 +3,7 @@
     and is published under the AGPL license. */
 
 class Bootstrap extends Zend_Application_Bootstrap_Bootstrap {
-    protected static $libns = array('gisconverter', 'phptojs', 'pwdgen');
+    protected static $libns = array('gisconverter', 'phptojs', 'pwdgen', 'alti');
 
     public function _bootstrap($resource = null) {
         $autoloader = Zend_Loader_Autoloader::getInstance();
index 58eb837..ef2aba5 100644 (file)
@@ -25,6 +25,10 @@ resources.frontController.plugins.loggeduser = "Syj_Controllers_Plugins_SyjLogge
 resources.frontController.plugins.contenttype = "Syj_Controllers_Plugins_SyjContentType"
 resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts/"
 resources.db.adapter = "Pdo_Pgsql"
+; altiphp
+resources.frontController.params.profileCache = APPLICATION_PATH "/../generated/profile/"
+resources.frontController.altiphp.source = "srtmtiles"
+resources.frontController.altiphp.cache = APPLICATION_PATH "/../generated/tiles"
 ; i18n
 resources.translate.adapter = gettext
 resources.translate.data = APPLICATION_PATH "/languages"
@@ -109,6 +113,12 @@ resources.router.routes.faq.defaults.action = "index"
 resources.router.routes.nominatim.route = "nominatim/"
 resources.router.routes.nominatim.defaults.controller = "nominatim"
 resources.router.routes.nominatim.defaults.action = "index"
+; profile/
+resources.router.routes.profile.type = "Zend_Controller_Router_Route_Regex"
+resources.router.routes.profile.route = "profile/(.*)\.png"
+resources.router.routes.profile.map.1 = "url"
+resources.router.routes.profile.defaults.controller = "profile"
+resources.router.routes.profile.defaults.action = "index"
 
 ; database
 resources.db.params.username = "arno"
index 043b199..0a6c468 100644 (file)
@@ -28,6 +28,8 @@ class ErrorController extends Zend_Controller_Action
                     $error_code = 400; // Bad Request
                 } else if ($error->exception instanceof Syj_Exception_Forbidden) {
                     $error_code = 403; // Forbidden
+                } else if ($error->exception instanceof Syj_Exception_NotImplemented) {
+                    $error_code = 501; // Not Implemented
                 } else if ($error->exception instanceof Syj_Exception_NotFound) {
                     $error_code = $error->exception->getCode();
                 }
index 9bfc6bf..997be3d 100644 (file)
@@ -59,6 +59,7 @@ class IdxController extends Zend_Controller_Action
             $this->view->headScript()->prependScript((string) $jsgeom);
             $this->view->loginform->login_geom_id->setValue((string)$path->id);
             $this->view->geomform->geom_title->setValue($path->title);
+            $this->view->profileActive = $this->_hasAltiProfile($path);
         } else {
             $this->_setInitialPos();
             $title = "Show your journey";
@@ -215,4 +216,30 @@ class IdxController extends Zend_Controller_Action
         return round($size) . $sizes[$c];
     }
 
+    private function _hasAltiProfile($path) {
+        if (!extension_loaded('gd')) {
+            return false;
+        }
+        $cachefile = $path->getProfileCache('small');
+        if (file_exists($cachefile)) {
+            return filesize($cachefile) != 0;
+        }
+
+        try {
+            $service = $this->_helper->SyjAltiService->service();
+        } catch(Exception $e) {
+            return false;
+        }
+
+        try {
+            $path->getAltiProfile($service, 2 / 100);
+            return true;
+        } catch(Syj_Exception_NotImplemented $e) {
+            @touch($cachefile);
+            return false;
+        } catch(Exception $e) {
+            return false;
+       }
+    }
+
 }
diff --git a/application/controllers/ProfileController.php b/application/controllers/ProfileController.php
new file mode 100644 (file)
index 0000000..e2a7801
--- /dev/null
@@ -0,0 +1,380 @@
+<?php
+/*  This file is part of Syj, Copyright (c) 2010-2011 Arnaud Renevier,
+    and is published under the AGPL license. */
+
+class Canvas {
+    protected $picture;
+
+    protected $width;
+    protected $height;
+    protected $margint;
+    protected $marginr;
+    protected $marginb;
+    protected $marginl;
+
+    protected $minx;
+    protected $miny;
+    protected $maxx;
+    protected $maxy;
+    protected $xdelta;
+    protected $ydelta;
+
+    protected $fontfile;
+    protected $fontsize;
+
+    function __construct($width, $height, $margint, $marginr, $marginb, $marginl) {
+        $this->width = $width;
+        $this->height = $height;
+        $this->margint = $margint;
+        $this->marginr = $marginr;
+        $this->marginb = $marginb;
+        $this->marginl = $marginl;
+
+        $this->minx = 0;
+        $this->miny = 0;
+        $this->maxx = $this->width;
+        $this->maxy = $this->height;
+
+        $this->picture = imagecreatetruecolor ($this->width, $this->height);
+    }
+
+    function __destruct() {
+        if ($this->picture) {
+            imagedestroy ($this->picture);
+        }
+    }
+
+    function setViewBox($minx, $miny, $maxx, $maxy) {
+        $this->xdelta = pow(10, floor(log10($maxx)));
+        $this->maxx = ceil ($maxx / $this->xdelta) * $this->xdelta;
+        $this->minx = floor ($minx / $this->xdelta) * $this->xdelta;
+
+        $this->ydelta = pow(10, floor(log10($maxy)));
+        $this->maxy = ceil ($maxy / $this->ydelta) * $this->ydelta;
+        $this->miny = floor ($miny / $this->ydelta) * $this->ydelta;
+
+        return $this;
+    }
+
+    function getViewBox() {
+        return array($this->minx, $this->miny, $this->maxx, $this->maxy);
+    }
+
+    function setThickness($thickness) {
+        imagesetthickness ($this->picture, $thickness);
+
+        return $this;
+    }
+
+    function setFont($fontfile, $fontsize) {
+        $this->fontfile = $fontfile;
+        $this->fontsize = $fontsize;
+
+        return $this;
+    }
+
+    function text($x, $y, $text, $color, $pos = "lt", $limits = array(null, null, null, null)) {
+        $bbox = imagettfbbox($this->fontsize, 0, $this->fontfile, $text);
+
+        switch (substr($pos, 0, 1)) {
+            case "r":
+                $x -= $bbox[2] - $bbox[0];
+            break;
+            case "c":
+                $x -= ($bbox[2] - $bbox[0]) / 2;
+            break;
+            case "l":
+            default:
+            break;
+        }
+
+        switch (substr($pos, 1, 1)) {
+            case "b":
+                $y -= $bbox[7] - $bbox[1];
+            break;
+            case "c":
+                $y -= ($bbox[7] - $bbox[1]) / 2;
+            break;
+            case "t":
+            default:
+            break;
+        }
+
+        list($limtop, $limright, $limbottom, $limleft) = $limits;
+
+        if (!is_null($limtop)) {
+            $y = max($limtop, $y);
+        }
+        if (!is_null($limright)) {
+            $x = min($limright, $x);
+        }
+        if (!is_null($limbottom)) {
+            $y = min($limbottom, $y);
+        }
+        if (!is_null($limleft)) {
+            $x = max($limleft, $x);
+        }
+
+        imagettftext($this->picture, $this->fontsize, 0, $x, $y, $this->color($color), $this->fontfile, $text);
+    }
+
+    function rect($x1, $y1, $x2, $y2, $color, $translate = false) {
+        imagefilledrectangle ($this->picture, $x1, $y1, $x2, $y2, $this->color($color));
+
+        return $this;
+    }
+
+    function line($x1, $y1, $x2, $y2, $color) {
+        imageline ($this->picture, $x1, $y1, $x2, $y2, $this->color($color));
+
+        return $this;
+    }
+
+    function draw($filename = null) {
+        imagepng($this->picture, $filename);
+
+        return $this;
+    }
+
+    private $colorcache = array();
+    function color($str) {
+        if (!isset ($this->colorcache[$str])) {
+            $r = "0x" . substr($str, 0, 2);
+            $g = "0x" . substr($str, 2, 2);
+            $b = "0x" . substr($str, 4, 2);
+            $this->colorcache[$str] = imagecolorallocate ($this->picture, $r, $g, $b);
+        }
+        return $this->colorcache[$str];
+    }
+
+    function canvasXPos($x) {
+        return round(($x - $this->minx) * ($this->width - $this->marginr - $this->marginl) / ($this->maxx - $this->minx)) + $this->marginl;
+    }
+    function canvasYPos($y) {
+        return $this->height - round(($y - $this->miny) * ($this->height - $this->marginb - $this->margint) / ($this->maxy - $this->miny)) - $this->margint;
+    }
+}
+
+class ProfileController extends Zend_Controller_Action {
+    public function init() {
+        if (!extension_loaded('gd')) {
+            throw new Syj_Exception_NotImplemented("gd is not installed");
+        }
+        $this->_helper->SyjNoRender->disableRender();
+    }
+
+    public function indexAction() {
+        $url = $this->getRequest()->getUserParam('url');
+        if (!isset($url)) {
+            throw new Syj_Exception_NotFound('Not Found', 404);
+        }
+
+        $pathMapper = new Syj_Model_PathMapper();
+        $path = new Syj_Model_Path();
+        if (!$pathMapper->findByUrl($url, $path)) {
+            if (is_numeric($url) and $pathMapper->hasexisted($url)) {
+                throw new Syj_Exception_NotFound('Gone', 410);
+            } else {
+                throw new Syj_Exception_NotFound('Not Found', 404);
+            }
+        }
+
+        $size = $this->getRequest()->getQuery('size', 'big');
+        if ($size == 'small') {
+            $width = 300;
+            $height = 225;
+        } else {
+            $width = 800;
+            $height = 600;
+            $size = 'big';
+        }
+
+        $file = $path->getProfileCache($size);
+        if (file_exists($file)) {
+            if (filesize($file) == 0) {
+                throw new Syj_Exception_NotImplemented("could not compute altitude profile");
+            }
+            $this->sendFile($file);
+            return;
+        }
+
+        try {
+            $service = $this->_helper->SyjAltiService->service();
+            /* we accept 2% of invalid values in the profile */
+            $profile = $path->getAltiProfile($service, 2 / 100);
+        } catch(Syj_Exception_NotImplemented $e) {
+            @touch($file);
+            throw $e;
+        }
+
+        $canvas = $this->drawProfile($profile, $width, $height);
+        $canvas->draw($file);
+        $this->sendFile($file);
+    }
+
+    protected function drawProfile($profile, $width, $height) {
+        $margint = 50;
+        $marginr = 50;
+        $marginb = 50;
+        $marginl = 50;
+
+        $last = end($profile);
+        $maxdist = $last[0];
+
+        $minalti = array(array(0, INF));
+        $maxalti = array(array(0, -INF));
+        foreach ($profile as $coords) {
+            $alti = $coords[1];
+
+            if ($alti == $minalti[0][1]) {
+                $minalti []= array($coords[0], $alti);
+            } else if ($alti < $minalti[0][1]) {
+                $minalti = array(array($coords[0], $alti));
+            }
+
+            if ($alti == $maxalti[0][1]) {
+                $maxalti []= array($coords[0], $alti);
+            } else if ($alti > $maxalti[0][1]) {
+                $maxalti = array(array($coords[0], $alti));
+            }
+        }
+
+        $canvas = new Canvas($width, $height, $margint, $marginr, $marginb, $marginl);
+        $canvas->setViewBox(0, $minalti[0][1], $maxdist, $maxalti[0][1]);
+
+        $canvas->rect(0, 0, $width, $height, "FFFFFF");
+
+        $slopecolor = function ($slope) {
+            if ($slope > 18) {
+                return "FF0000"; // red
+            } else if ($slope > 12) {
+                return "FF4000";
+            } else if ($slope > 7) {
+                return "FF8000";
+            } else if ($slope > 3) {
+                return "FFC000";
+            } else if ($slope > 0) {
+                return "FFFF00";  // yellow
+            } else if ($slope > -3) {
+                return "00FFFF";
+            } else if ($slope > -7) {
+                return "00C0FF";
+            } else if ($slope > -12) {
+                return "0080FF";
+            } else if ($slope > -18) {
+                return "0040FF";
+            } else {
+                return "0000FF"; // blue;
+            }
+        };
+
+        $canvas->setThickness (2);
+
+        $prev = null;
+        foreach ($profile as $coord) {
+            if (!is_null($prev)) {
+                $dist = $coord[0];
+                $alti = $coord[1];
+                $prevdist = $prev[0];
+                $prevalti = $prev[1];
+
+                $slope = 100 * ($alti - $prevalti) / ($dist - $prevdist) ;
+                $canvas->line($canvas->canvasXPos($prevdist), $canvas->canvasYPos($prevalti),
+                              $canvas->canvasXPos($dist), $canvas->canvasYPos($alti),
+                              $slopecolor($slope), true);
+            }
+            $prev = $coord;
+        }
+
+        $canvas->setThickness (1);
+        $canvas->setFont(APPLICATION_PATH . '/resources/' . 'DejaVuSans.ttf', 10);
+
+        list ($minx, $miny, $maxx, $maxy) = $canvas->getViewBox();
+
+        $deltax = pow(10, floor(log10($maxx - $minx)));
+        foreach (range($minx, $maxx, $deltax) as $x) {
+            $canvas->line($canvas->canvasXPos($x), $height - $marginb + 10, $canvas->canvasXPos($x), $height - $marginb, "000000");
+            if ($deltax < 1000) {
+                $text = $x . "m";
+            } else {
+                $text = round($x / 1000) . "km";
+            }
+            $canvas->text($canvas->canvasXPos($x), $height - $marginb + 10 + 3, $text, "000000", "cb");
+        }
+        $canvas->line($marginl, $height - $marginb, $width - $marginr, $height - $marginb, "000000");
+
+        $deltay = pow(10, floor(log10($maxy - $miny)));
+        foreach (range($miny, $maxy, $deltay) as $y) {
+            $canvas->line($marginl - 10, $canvas->canvasYPos($y), $marginl, $canvas->canvasYPos($y), "000000");
+            $text = $y . "m";
+            $canvas->text($marginl - 10 - 3, $canvas->canvasYPos($y), $text, "000000", "rc");
+        }
+        $canvas->line($marginl, $height - $marginb, $marginr, $margint, "000000");
+
+        list($minx, $miny, $maxx, $maxy) = $canvas->getViewBox();
+        $limits = array($canvas->canvasYPos($maxy) + 3, $canvas->canvasXPos($maxx) - 3, $canvas->canvasYPos($miny) - 3, $canvas->canvasXPos($minx) + 3);
+
+        $prev = null;
+        foreach ($minalti as $coords) {
+            list ($dist, $alti) = $coords;
+            if (!is_null($prev)) {
+                $xdist = $canvas->canvasXPos($dist) - $canvas->canvasXPos($prev[0]);
+                /* second label would be less than 10px from previous one. Do
+                not display it */
+                if ($xdist < 10) {
+                    continue;
+                }
+            }
+            $text = round($alti) . "m";
+            $xpos = $canvas->canvasXPos($dist);
+            $ypos = $canvas->canvasYPos($alti);
+            $canvas->text($xpos, $ypos + 5, $text, "000000", "cb", $limits);
+            $canvas->rect($xpos - 1, $ypos - 1, $xpos + 1, $ypos + 1, "000000");
+            $prev = $coords;
+        }
+
+        $prev = null;
+        foreach ($maxalti as $coords) {
+            list ($dist, $alti) = $coords;
+            if (!is_null($prev)) {
+                $xdist = $canvas->canvasXPos($dist) - $canvas->canvasXPos($prev[0]);
+                /* second label would be less than 10px from previous one. Do
+                not display it */
+                if ($xdist < 10) {
+                    continue;
+                }
+            }
+            $text = round($alti) . "m";
+            $xpos = $canvas->canvasXPos($dist);
+            $ypos = $canvas->canvasYPos($alti);
+            $canvas->text($xpos, $ypos - 5, $text, "000000", "ct", $limits);
+            $canvas->rect($xpos - 1, $ypos - 1, $xpos + 1, $ypos + 1, "000000");
+            $prev = $coords;
+        }
+
+        return $canvas;
+    }
+
+    protected function sendFile($file) {
+        $lastmodified = filemtime($file);
+
+        $request = $this->getRequest();
+        $response = $this->getResponse();
+
+        $response->setHeader('Content-Type', 'image/png', true)
+                 ->setHeader('Content-Length', filesize($file), true);
+
+        if ($request->getServer("HTTP_IF_MODIFIED_SINCE")) {
+            if ($lastmodified <= strtotime($request->getServer("HTTP_IF_MODIFIED_SINCE"))) {
+                $response->setHttpResponseCode(304);
+                return;
+            }
+        }
+
+                 // no-cache is needed otherwise IE does not try to get new version.
+        $response->setHeader ('Cache-control', 'no-cache, must-revalidate', true)
+                 ->setHeader("Last-Modified", gmdate("D, d M Y H:i:s", filemtime($file)) . " GMT", true);
+
+        readfile($file);
+    }
+}
diff --git a/application/controllers/helpers/SyjAltiService.php b/application/controllers/helpers/SyjAltiService.php
new file mode 100644 (file)
index 0000000..e3d44b3
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+/*  This file is part of Syj, Copyright (c) 2010-2011 Arnaud Renevier,
+    and is published under the AGPL license. */
+
+
+class Syj_Controller_Action_Helper_SyjAltiService extends Zend_Controller_Action_Helper_Abstract
+{
+    protected static $_service = null;
+
+    public function service() {
+        if (is_null (self::$_service)) {
+            $params = Zend_Controller_Front::getInstance()->getParam('altiphp');
+            if ($params['source'] == 'srtmtiles' and isset($params['cache'])) {
+                $cachedir = $params['cache'];
+                if (!is_dir($cachedir)) {
+                    if (@mkdir($cachedir, 0755, true) === false) {
+                        throw new Zend_Exception();
+                    }
+                }
+            }
+            self::$_service = new alti\Alti($params);
+        }
+        return self::$_service;
+    }
+}
index 862b3a6..807f44b 100644 (file)
@@ -6,6 +6,9 @@ class Syj_Controller_Action_Helper_SyjReset extends Zend_Controller_Action_Helpe
 {
     public function resetPlaceHolders() {
         $controller = $this->getActionController();
+        if (!$controller->view) {
+            return;
+        }
         $controller->view->jslocales = null;
         $controller->view->headScript()->exchangeArray(array());
         $controller->view->headLink()->exchangeArray(array());
index 3bf13ea..a9f9c06 100644 (file)
@@ -7,6 +7,9 @@ class Syj_Controllers_Plugins_SyjLoggedUser extends Zend_Controller_Plugin_Abstr
     public function postDispatch(Zend_Controller_Request_Abstract $request) {
         $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
         $view = $viewRenderer->view;
+        if (!$view) {
+            return;
+        }
         $helper = Zend_Controller_Action_HelperBroker::getStaticHelper('SyjUserManager');
         $view->loggedUser = $helper->current();
     }
diff --git a/application/exception/NotImplemented.php b/application/exception/NotImplemented.php
new file mode 100644 (file)
index 0000000..58961be
--- /dev/null
@@ -0,0 +1,7 @@
+<?php
+/*  This file is part of Syj, Copyright (c) 2010-2011 Arnaud Renevier,
+    and is published under the AGPL license. */
+
+class Syj_Exception_NotImplemented extends Zend_Exception
+{
+}
index 28e5c90..6512f76 100644 (file)
@@ -83,4 +83,70 @@ class Syj_Model_Path extends Syj_Model_Generic
         return $this->_creator_ip;
     }
 
+    /*
+     * returns an array containing two arrays. First one is list of distance
+     * from start. Second on is altitude at that point.
+     * @error: ratio of void we accept
+     */
+    public function getAltiProfile($altiService, $error = 0) {
+        $points = $altiService->interpolate($this->_geom);
+        $points = array_map(function($point) {
+            if ($point instanceof \gisconverter\Geometry) {
+                return array($point->lon, $point->lat);
+            }
+            return $point;
+        }, $points);
+        $altitudes = $altiService->altitude($points);
+
+        if (is_null($altitudes)) {
+            throw new Syj_Exception_NotImplemented("could not compute altitude profile");
+        }
+
+        $res = array(array(0, $altitudes[0]));
+        $prevpoint  = $points[0];
+        $prevdist  = 0;
+
+        $alticount = count($altitudes);
+        $altinull = 0;
+        foreach (range(1, count($altitudes) - 1) as $idx) {
+            $point = $points[$idx];
+            $delta = $altiService->vincentyDistance($prevpoint[0], $prevpoint[1], $point[0], $point[1]);
+            $dist = $prevdist + $delta;
+            if ($delta == 0) { // we have two similar points side to side
+                continue;
+            }
+            if (is_null($altitudes[$idx])) {
+                $altinull++;
+                continue;
+            }
+            $prevpoint = $point;
+            $prevdist = $dist;
+            $res[] = array($dist, $altitudes[$idx]);
+        }
+
+        if ($altinull / $alticount > $error) {
+            throw new Syj_Exception_NotImplemented("too many void in altitude profile");
+        }
+        return $res;
+    }
+
+    public function getProfileCache($size) {
+        $cacheDir = Zend_Controller_Front::getInstance()->getParam('profileCache');
+        if (is_file($cacheDir) and !is_dir($cacheDir)) {
+            throw new Zend_Exception();
+        }
+        if (!is_dir($cacheDir)) {
+            if (@mkdir($cacheDir, 0755, true) === false) {
+                throw new Zend_Exception();
+            }
+        }
+
+        return sprintf("%s/%s-%s.png", $cacheDir, (string)$this->_id, $size);
+    }
+
+    public function invalidateCache() {
+        @unlink($this->getProfileCache('small'));
+        @unlink($this->getProfileCache('big'));
+    }
+
 }
index 7d514e9..2bbeabb 100644 (file)
@@ -80,6 +80,7 @@ class Syj_Model_PathMapper
             $path->id = $this->getDbTable()->insert($data);
         } else {
             $this->getDbTable()->update($data, array('id = ?' => $id));
+            $path->invalidateCache();
         }
     }
 
diff --git a/application/resources/DejaVuSans.ttf b/application/resources/DejaVuSans.ttf
new file mode 100644 (file)
index 0000000..27cff47
Binary files /dev/null and b/application/resources/DejaVuSans.ttf differ
diff --git a/application/views/helpers/FullBaseUrl.php b/application/views/helpers/FullBaseUrl.php
new file mode 100644 (file)
index 0000000..e69de29
index 1e34d35..f5b7b67 100644 (file)
@@ -96,9 +96,19 @@ if ($this->errorMsg) {
                                         $this->translate('gpx export'),
                                         $this->translate('gpx export'));
                        ?>
+                </div>
+
+                <?php if ($this->profileActive) { ?>
+                <div id="path-profile-title" class="info-title"><?php echo $this->translate("altitude profile")?></div>
+                <div id="path-profile-content" class="info-content"><?php
+                        $urlcomp = urlencode($this->path->urlcomp ?: $this->path->id);
+                        $href = $this->baseUrl() . '/profile/' . $urlcomp . '.png';
+                        $imgsrc =  $this->addParamToUrl($href, 'size', 'small', true);
+                        printf('<a href="%s"><img src="%s"></a>', $href, $imgsrc);
+                ?></div>
+                <?php }?>
 
 
-                </div>
             </div>
         </div>
     <?php }?>
diff --git a/library/alti.php b/library/alti.php
new file mode 120000 (symlink)
index 0000000..11a2f35
--- /dev/null
@@ -0,0 +1 @@
+altiphp/alti.php
\ No newline at end of file
diff --git a/library/altiphp b/library/altiphp
new file mode 160000 (submodule)
index 0000000..f3de329
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit f3de32954675440c16f0992aeb79142eb87ba7f2
index 8c48c99..2f3f676 100644 (file)
@@ -674,6 +674,20 @@ var SYJView = {
           return;
       }
 
+        $('path-profile-content').select('img').each(function(img) {
+            var src = img.src;
+            var found = src.match('foo=bar(\\d*)');
+            var num;
+            if (found) {
+                num = parseInt(found[1], 10);
+                img.src = src.replace(found[0], 'foo=bar' + (num+1));
+            } else if (src.indexOf('?') === -1) {
+                img.src = img.src + '?foo=bar0';
+            } else {
+                img.src = img.src + '&foo=bar0';
+            }
+        });
+
       this.messenger.setMessage(SyjStrings.saveSuccess, "success");
       SYJDataUi.viewmode();
       document.title = $('geom_title').value;