<?php
/******************************************************************************
 *  SiteBar 3 - The Bookmark Server for Personal and Team Use.                *
 *  Copyright (C) 2003,2004  Ondrej Brablc <http://brablc.com/mailto?o>       *
 *                                                                            *
 *  This program is free software; you can redistribute it and/or modify      *
 *  it under the terms of the GNU General Public License as published by      *
 *  the Free Software Foundation; either version 2 of the License, or         *
 *  (at your option) any later version.                                       *
 *                                                                            *
 *  This program is distributed in the hope that it will be useful,           *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
 *  GNU General Public License for more details.                              *
 *                                                                            *
 *  You should have received a copy of the GNU General Public License         *
 *  along with this program; if not, write to the Free Software               *
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
 ******************************************************************************/

if (@!include_once('./inc/config.inc.php'))
{
    header('Location: config.php');
    exit;
}

require_once('./inc/errorhandler.inc.php');
require_once('./inc/page.inc.php');
require_once('./inc/tree.inc.php');
require_once('./inc/usermanager.inc.php');

class SiteBar extends ErrorHandler
{
    var $linkMenu;
    var $nodeMenu;
    var $userMenu;
    var $um;
    var $tree;
    var $expandedNodes;
    var $treearr = array();

    var $iconnect;
    var $iempty;
    var $ijoin;
    var $ijoinl;
    var $ilink;
    var $ilinkp;
    var $nmenu = null;
    var $lmenu = null;

    var $sortMode;
    var $useHitCounter = false;
    var $compactMode = false;

    var $sw;
    var $linkCount = 0;
    var $nodeCount = 0;

    function SiteBar()
    {
        $this->sw = new StopWatch();
        $this->sw->start();

        $this->um =& UserManager::staticInstance();
        $this->tree =& Tree::staticInstance();

        $this->expandedNodes = $this->getExpandedNodes('SB3NODES');

        if (isset($_REQUEST['reload']) && $_REQUEST['reload'] == 'all')
        {
            // Temporarily disable hiding of folders
            $this->um->hiddenFolders = array();
        }

        $this->nodeMenu = array
        (
            'Add Link:*i',
            'Add Folder:*i',
            '',
            'Hide Folder:*',
            'Unhide Subfolders:*',
            'Folder Properties:*u',
            'Delete Folder:*d',
            'Purge Folder:p',
            'Undelete:pi',
            '',
            'Copy:*:nodeCopy',
            'Paste:*i_c',
            '',
            'Import Bookmarks:*i',
            'Export Bookmarks:*',
            'Security:*',
        );

        $this->linkMenu = array
        (
            'Email Link',
            'Copy Link::linkCopy',
            'Delete Link:d',
            '',
            'Properties:u',
        );

        $this->userMenu = array
        (
            'Log In',
            'Sign Up',
            'User Settings',
            'Verify Email',
            'Membership',
            '',
            'SiteBar Settings',
            'Maintain Trees',
            'Maintain Users',
            'Maintain Groups',
            '',
            'Contact Admin',
            'Help',
            'Log Out',
        );

        Skin::set($this->um->getParam('user','skin'));

        $this->iconnect = Skin::img('connect');
        $this->iempty   = Skin::img('empty');
        $this->ijoin    = Skin::img('join');
        $this->ijoinl   = Skin::img('join_last');
        $this->ilink    = Skin::img('link');
        $this->ilinkp   = Skin::img('link_private');

        if ($this->um->getParam('user','menu_icon'))
        {
            $this->lmenu = "<span class='menu' onclick='return menuOn(event,this.parentNode);'>" .
                Skin::img('menu') . "</span>";
            $this->nmenu = "<span class='menu' onclick='return menuOn(event,this.parentNode.parentNode.parentNode);'>" .
                Skin::img('menu') . "</span>";
        }

        $this->sortMode = $this->um->getParam('user','link_sort_mode');
        $this->useHitCounter = $this->um->getParam('config','use_hit_counter');

        if (!$this->useHitCounter)
        {
            if (!in_array($this->sortMode,array('abc','changed')))
            {
                $this->sortMode = 'abc';
            }
        }
    }

    function run()
    {
        $this->compactMode = isset($_REQUEST['mode'])&&$_REQUEST['mode']=='compact';

        // If we want to use compression and it is not used yet
        if ($this->um->getParam('config','use_compression')
        &&  !@ini_get('zlib.output_compression'))
        {
            ob_start('ob_gzhandler');
        }

        $this->drawHeader();

        if ($this->um->setupDone)
        {
            $roots = array();

            if (isset($_REQUEST['root']))
            {
                $root = $this->tree->getNode($_REQUEST['root']);

                if (!$root)
                {
                    die("Invalid folder id!");
                }

                $acl = $root->getACL();
                if (!$acl || !$acl['allow_select'])
                {
                    die("Access denied!");
                }

                $root->level = 0;

                $roots[] = $root;
            }
            else
            {
                $roots = $this->tree->loadRoots();
            }

            foreach ($roots as $root)
            {
                echo '<div class="root tree">'."\n";
                // Late node loading
                $this->tree->loadNodes($root, false);
                $this->drawNode($root);
                echo "</div>\n";
            }
        }
        else
        {
            $this->userMenu = array('Set Up');
        }

        $this->drawFooter();
    }

    function drawHeader()
    {
        $extern = $this->um->getParam('user','extern_commander')?"0":"1";
        $defaultSearch = $this->um->getParam('user','default_search');
        $inPlaceCmds = implode("','", $this->um->inPlaceCommands());

        if ($this->um->getParam('user','keep_in_cache'))
        {
            header("Cache-Control: max-age=". $this->um->getParam('user','keep_in_cache')*60*60);
        }

        Page::head(null, null, "initPage($extern,'$defaultSearch', new Array('$inPlaceCmds'))");

        // Include skin hook file
        include_once(Skin::path().'/hook.php');

        if (!$this->compactMode && $this->um->getParam('config','show_logo'))
        {
            Hook::head();
        }

        $this->writeMenu('node', $this->nodeMenu);
        $this->writeMenu('link', $this->linkMenu);

        if (!$this->compactMode)
        {

        // There must not be any place between the images, therefore
        // those funny tag endings.

        $coloring = <<<__COLORING
             onmousedown="this.className='pressed'"
             onmouseup="this.className='raised'"
             onmouseover="this.className='raised'"
             onmouseout="this.className=''"
__COLORING;

?>
<div id="toolbar">
    <div id="tlbSearch">
        <input id="fldSearch" type="text" onkeyup="if (event && event.keyCode==13) search();"
       ><img id="btnSearch" src="<?php echo Skin::imgsrc('search')?>" title="<?php echo T("Search")?>"
             onclick="search()" <?php echo $coloring?> alt="S"
       ><img id="btnHighlight" src="<?php echo Skin::imgsrc('highlight')?>" title="<?php echo T('Highlight')?>"
             onclick="highlight()" <?php echo $coloring?> alt="H">
    </div>
    <div id="tlbOther">
        <img id="btnCollapse" src="<?php echo Skin::imgsrc('collapse')?>" title="<?php echo T('Collapse/Expand All')?>"
             onclick="collapseAll()" <?php echo $coloring?> alt="-"
       ><?php if ($this->um->getParam('user','use_hiding')) : ?><img id="btnReloadAll" src="<?php echo Skin::imgsrc('reload_all')?>" title="<?php echo T('Reload with Hidden Folders')?>"
             onclick="reloadAll()" <?php echo $coloring?> alt="R+"
       ><?php endif;?><img id="btnReload" src="<?php echo Skin::imgsrc('reload')?>" title="<?php echo T('Reload')?>"
             onclick="reloadPage()" <?php echo $coloring?> alt="R"
       >
    </div>
</div>
<?php
        }
    }

    function drawFooter()
    {
?>
<div class="hidden">
<div><a href="command.php?command=Log%20In"><?php echo T('Backup Log In')?></a></div>
<div><a href="command.php?command=Log%20Out"><?php echo T('Backup Log Out')?></a></div>
</div>
<?php
        if (!$this->compactMode)
        {
            $this->writeMenu('user', $this->userMenu);

            $this->sw->stop();

            $stat = array();

            if ($this->um->getParam('config','show_statistics')
            && (!$this->um->isAnonymous() || $this->um->getParam('config','allow_sign_up')))
            {
                $stat = array
                (
                    'links_shown' => $this->linkCount,
                    'nodes_shown' => $this->nodeCount,
                    'queries' => $this->um->db->count,
                    'time_db' => number_format($this->um->db->sw->elapsed,2),
                    'time_total' => number_format($this->sw->elapsed,2),
                    'time_pct' => intval($this->um->db->sw->elapsed/$this->sw->elapsed*100),
                );

                $this->um->statistics($stat);
                $this->tree->statistics($stat);
            }

            Hook::foot($this->um->config['release'],
                       $this->um->getParam('config','show_sponsor'),
                       $stat);
        }

        // Optimization for MSIE to keep messages in the cache
        if (Page::isMSIE())
        {
            $images = array
            (
                'collapse',
                'connect',
                'empty',
                'highlight',
                'join',
                'join_last',
                'link',
                'link_private',
                'menu',
                'minus',
                'minus_last',
                'node',
                'node_open',
                'plus',
                'plus_last',
                'reload',
                'reload_all',
                'root',
                'root_deleted',
                'root_plus'
            );

            echo "<div style='display: none'>";

            foreach ($images as $image)
            {
                echo Skin::img($image);
            }

            echo "</div>";
        }

        if ($this->hasErrors())
        {
            // Cannot be defined by skin
            echo "<div style='margin-top: 50px; color:yellow; background: red;'>";
            $this->writeErrors();
            echo "</div>";
        }

        Page::foot();
    }

    function drawNode($node, $last=false)
    {
        $nodename = 'n' . $node->id;
        $aclstr = '';

        foreach ($node->getACL() as $right => $value)
        {
            if (!$value) continue;
            list($pulp, $name) = explode('_',$right);
            if ($name{0}=='d' && !$node->id_parent && !$this->um->getParam('config','allow_user_tree_deletion'))
            {
                continue;
            }
            $aclstr .= $name{0};
        }

        $showACL = $this->um->getParam('user','show_acl');

        // For public mode we have to check it, otherwise we have it
        if ($showACL && $this->um->pmode)
        {
            $publicACL = $node->getGroupACL($this->um->config['gid_everyone']);
            $node->hasACL = $publicACL && $publicACL['allow_select'];
        }

        if (!$showACL)
        {
            $node->hasACL = false;
        }

        /* Init images */
        $inode      = Skin::img('node', 'n', $node->id);
        $inodeo     = Skin::img('node_open', 'n', $node->id);
        $iplus      = Skin::img('plus', 's', $node->id);
        $iplusl     = Skin::img('plus_last', 's', $node->id);
        $iminus     = Skin::img('minus', 's', $node->id);
        $iminusl    = Skin::img('minus_last', 's', $node->id);

        echo '<div id="' . $nodename . '"'.
             ' class="node"'.
             ($node->comment?' title="'. Page::quoteValue($node->comment) . '"':'') .
             ' x_acl="'. $aclstr . ($node->deleted_by?'':'*') .'"'.
             ' x_level="' . $node->level . '"'.
             '><span'.
             ' onclick="node(event,this.parentNode)"'.
             ' oncontextmenu="return menuOn(event,this.parentNode)">';

        $this->nodeCount++;

        $showChildren = isset($this->expandedNodes[$node->id]) &&
            $this->expandedNodes[$node->id]=='Y';

        $this->tree->loadLinks($node, $this->sortMode);

        if ($node->level)
        {
            if ($node->childrenCount()==0)
            {
                $iplus   = $this->ijoin;
                $iplusl  = $this->ijoinl;
                $iminus  = $this->ijoin;
                $iminusl = $this->ijoinl;
            }
            echo implode('',$this->treearr) .
                ($last?($showChildren?$iminusl:$iplusl)
                      :($showChildren?$iminus :$iplus));

            array_push($this->treearr,($last?$this->iempty:$this->iconnect));
        }
        else
        {
            if ($node->deleted_by==null)
            {
                $inodeo = Skin::img('root', 'n', $node->id, 'root');
                if ($node->childrenCount()==0)
                {
                    $inode  = $inodeo;
                }
                else
                {
                    $inode  = Skin::img('root_plus', 'n', $node->id, 'root');
                }
            }
            else
            {
                $inode  = Skin::img('root_deleted', 'n', $node->id, 'root');
                $inodeo = $inode;
            }
        }

        echo '<a id="'.$nodename.'l" href="javascript:void('.$node->id.')"'.
             ($node->hasACL?' class="acl"':'') .
             Page::dragDropNode($node->id).
             '>'.
             ($showChildren?$inodeo:$inode) .
             $this->nmenu .
             Page::quoteValue($node->name) .
             "</a></span>\n".

             '<div id="c' . $node->id . '" '.
             ' class="children'. ($showChildren?'Expanded':'Collapsed') .'">'."\n";

        $count = $node->childrenCount();

        foreach ($node->getChildren() as $child)
        {
            $count--;

            if ($child->type_flag=='n')
            {
                $this->drawNode($child, $count==0);
            }
            else
            {
                $this->drawLink($child, $aclstr, $count==0);
            }
        }

        if ($node->level)
        {
            array_pop($this->treearr);
        }

        echo "</div>\n";
        echo "</div>\n";

        return true;
    }

    function drawLink($link, $aclstr, $last=false)
    {
        $linkname = "l" . $link->id;

        echo '<div class="link" id="' . $linkname . '"'.
             ' onclick="return lnk(event,this)"'.
             ' oncontextmenu="return menuOn(event,this)"'.
             ' x_acl="'.$aclstr.'"'.
             '>'."\n";

        $ifavicon = '';

        if ($link->favicon && $this->um->getParam('user','use_favicons'))
        {
            if ($link->favicon && $this->um->getParam('config','use_favicon_cache'))
            {
                $link->favicon = 'favicon.php?' . md5($link->favicon) . '=' . $link->id;
            }

            $ifavicon ='<img class="favicon" alt="" src="'.$link->favicon.'"'.
                   ' onerror="this.src=\''.Skin::imgsrc("link_wrong_favicon").'\'">';
        }
        else
        {
            $ifavicon = $link->private?$this->ilinkp:$this->ilink;
        }

        echo implode("",$this->treearr) . ($last?$this->ijoinl:$this->ijoin);

        $url = $link->url;

        $target = null;

        $isBookmarklet = $link->url{0}=='j' && strpos($link->url,'javascript:')!==false;

        if (!$isBookmarklet)
        {
            $target = Page::target();
            if ($this->useHitCounter)
            {
                $url = 'go.php?'. (strstr($link->url,'id=')?'sbl':'') .'id='.$link->id.'&amp;url='.$link->url;
            }
        }

        $link_name = $link->name;

        if (strlen($link->sort_info))
        {
            $link_name .= ' (' . $link->sort_info . ')';
        }

        $class = '';

        if ($link->private)
        {
            $class .= ' private';
        }
        if ($link->is_dead)
        {
            $class .= ' dead';
        }

        echo ($this->lmenu?$ifavicon.$this->lmenu:'') .
             '<a '. ($link->comment?' title="'. Page::quoteValue(substr($link->comment,0,255)) . '"':'').
             ($class?" class=\"$class\"":'') .
             ' href="' . Page::quoteValue($url) . '"'.
             $target.
             Page::dragDropLink($link->id_parent,$link->id).
             '>'.
             ($this->lmenu?'':$ifavicon).
             Page::quoteValue($link_name) . '</a></div>'."\n";

        $this->linkCount++;
    }

    function writeMenu($type, $items)
    {
        $prev = '';
        $allowedItems = array();
        $allowedACL = array();
        $functions = array();

        foreach ($items as $command)
        {
            $parts = explode(':', $command);
            $command = array_shift($parts);
            $acl = array_shift($parts);
            $function = array_shift($parts);

            if (($command && !$this->um->isAuthorized($command))
            ||  (!$command && !$prev))
            {
                continue;
            }
            $allowedItems[]=$command;
            $allowedACL[]=$acl;
            $functions[]=$function;
            $prev = $command;
        }

?>
    <div id='<?php echo $type?>CtxMenu' class='menu'>
<?php
        for ($i=0; $i<count($allowedItems); $i++)
        {
            $command = $allowedItems[$i];

            if (!$command && $i==count($allowedItems)-1)
            {
                continue;
            }

            $div = "\t<div id='${type}Item$i' class='".
                ($command?'off':'separator')."'".
                ' onmouseover="itemOn(this)"'.
                ' onmouseout="itemOff(this)"';

            if ($command)
            {
                $div .=
                    ' onclick="itemDo(this'.($functions[$i]?',\''.$functions[$i].'\'':''). ')"'.
                    ' x_acl="'.$allowedACL[$i].'" '.
                    ' x_cmd="'.$command.'" ';
            }

            echo $div . '>' . T($command) . "</div>\n";
            $prev = $command;
        }
?>
    </div>
<?php
    }

    function getExpandedNodes($cookieName)
    {
        $states = null;
        $nodes = array();

        if (isset($_COOKIE[$cookieName]))
        {
            $states = explode(':', $_COOKIE[$cookieName]);

            // Remove last element that is either marker ! or possibly incomplete
            array_pop($states);

            while ($node = array_pop($states))
            {
                $nodes[substr($node,1)] = $node{0};
            }
        }
        else
        {
            // Expand the first tree if only one loaded
            $states = '';
            $roots = $this->tree->loadRoots();
            $count = 0;

            foreach ($roots as $root)
            {
                if ($root->acl['allow_select'])
                {
                    $count++;
                }
            }

            if ($count==1)
            {
                foreach ( $roots as $root)
                {
                    if ($root->acl['allow_select'])
                    {
                        $nodes[$root->id] = 'Y';
                        break;
                    }
                }
            }
        }

        $states = '';
        foreach ($nodes as $node => $val)
        {
            if ($val == 'Y')
            {
                $states .= $val.$node.':';
            }
        }

        $this->um->setCookie($cookieName, $states.'!');

        return $nodes;
    }
}

/*** Run SiteBar Application **************************************************/

$sb = new Sitebar();
$sb->run();
?>
