<?php
/**
 * Content component: TableEditor
 *
 * $Id$
 *
 * @author gERD Schaufelberger <gerd@schaufelberger.de>
 * @license PHP License
 * @package WB
 * @subpackage content
 */

/**
 * Load required class
 */
WBClass::load('WBContent');

/**
 * Content component: TableEditor
 *
 * @version 1.5.3
 * @package WB
 * @subpackage content
 */
class WBContent_TableEditor extends WBContent
{
   /**
    * my parameter list
    *
    * - action either "moveup", "movedown", "list", "add", "edit", "rm", "display"
    * - requiredgroup (tableeditor)
    * - requiredgroupmanager (tableadmin)
    * - showsomeoneelses (1) display other users records, too
    * - table (blog)
    * - tableenv if table env is set, it will be used to load config and templates
    * - limit (20)
    * - goto pager's page (0)
    * - id (__new)
    * - managesortcolumn column name or empty ("")
    * - levels number of levels to move, if managesortcolumn applies (1)
    * - order custom order column (only if managesortcolumn is empty)
    * - addcurrentuser either "new", "always" or "never" ("new")
    * - searchfields (array()) list of columns that are allowed to search in using LIKE
    * - usefilter (0) filter lists, display filter form, use form definition
    *   in filter.xml and filter.tmpl to render form.
    * - translator ("no"), "translate", "update", "auto"
    *
    * @var array
    */
    protected $config = array(
                'action'                =>  'list',
                'requiredgroup'         =>  'tableeditor',
                'requiredgroupmanager'  =>  'tableadmin',
                'showsomeoneelses'      =>  1,
                'table'                 =>  'blog',
                'tableenv'              =>  '',
                'limit'                 =>  '20',
                'goto'                  =>  '0',
                'id'                    =>  '__new',
                'managesortcolumn'      =>  '',
                'levels'                =>  1,
                'order'                 =>  '',
                'addcurrentuser'        =>  'new',
                'searchfields'          =>  array(),
                'usefilter'             =>  0,
                'translator'            =>  'no',
                'database'              =>  '__default'
                );

   /**
    * table
    * @var WBDatasource_Table
    */
    protected $table;

   /**
    * id of current record of table
    * @var string
    */
    protected $id  =   '__new';

    /**
     * relation table
     * @var WBDatasource_RelationTable
     */
    protected $relTable;

    /**
     * List of relation Data
     * @var array
     */
    protected $relList;

    /**
     * search and filter clause
     * @var array
     */
    private $clause =   array();

    /**
     * Select options for pager
     * @var array
     */
    private $options    =   array();

    /**
     * @var WBFormProcessor_Filter
     */
    protected $filter;

    /**
     * Environment was set up
     * @var bool
     */
    private $envOK  =   false;

    /**
     * Message Translations
     * @var patI18n_Importer
     */
    private $nlsImp;

    /**
     * NLS Saver
     * @var WBDatasource_NLS_Saver
     */
    protected $nlsSaver;

    /**
     * 2nd constructor
     */
    protected function init()
    {
        $params =   array();
        if ('__default' != $this->config['database']) {
            /** @var WBConfig */
            $config =   WBClass::create('WBConfig');

            // load default dabatabse
            $config->load('config');
            $params =   $config->get('db');

            // load config for additional databases
            $config->load('table/database');
            $params =   array_merge($params, $config->get('db/' . $this->config['database'], array()));
        }

        $this->table    =   WBClass::create('WBDatasource_Table', $params);
        $params =   array(
                            'table' =>  $this->table
                            );
        $this->nlsSaver =   WBClass::create('WBDatasource_NLS_Saver', $params);
    }

   /**
    * run
    *
    * run component
    *
    * @return array parameter list
    */
    public function run()
    {
        $this->addConfigAsGlobalVars();
        if (!$this->setupPermission()) {
            return $this->config;
        }
        $tmplDir    =   $this->setupEnv();
        $foreign    =   $this->setupForeign();
        $this->setupSortColumn();
        $p          =   $this->setupId($foreign);

        $this->nlsSaver->setTableName($this->config['table']);
        $this->nlsSaver->setId($this->id);
        $this->nlsSaver->enable(false);
        if (in_array($this->config['translator'], array('auto', 'on'))) {
            $this->nlsSaver->enable(true);
        }

        $this->addConfigAsGlobalVars();
        $this->initFilter();

        $this->runAction($p, $foreign, $tmplDir);

        return $this->config;
    }

    /**
     * Actually Run Action
     *
     * Switch-case for action parameter
     *
     * @param array
     * @param string
     * @param string
     */
    protected function runAction($p, $foreign, $tmplDir)
    {
        switch ($this->config['action']) {

            case 'moveup':
                $this->moveUp($p);
                $this->loadTemplates('list');
                break;
            case 'movedown':
                $this->moveUp($p, false);
                $this->loadTemplates('list');
                break;

            case 'add':
                $this->runAdd($p, $foreign, $tmplDir);
                break;

            case 'clone':
                $this->runClone($p, $foreign, $tmplDir);
                break;

            case 'edit':
                $this->runEdit($p, $foreign, $tmplDir);
                break;

            case 'rm':
                if ($this->runRm($p, $foreign, $tmplDir)) {
                    return;
                }
                $this->loadTemplates('list');
                break;

            case 'display':
                $this->runDisplay($p, $foreign, $tmplDir);
                break;

            case 'list':
            default:
                $this->loadTemplates('list');
                break;
        }

        $this->displayList($foreign);
    }

    /**
     * Actually Run Action: Display
     *
     * @param array
     * @param string
     * @param string
     */
    protected function runDisplay($p, $foreign, $tmplDir)
    {
        $this->tmpl->addGlobalVars($p);
        $this->loadTemplates('display');
    }

    /**
     * Actually Run Action: Add
     *
     * @param array
     * @param string
     * @param string
     */
    protected function runAdd($p, $foreign, $tmplDir)
    {
        $this->addRelatedData($p);
        $this->id  =   '__new';
        $this->processForm('edit', $p);
    }

    /**
     * Actually Run Action: Clone
     *
     * @param array
     * @param string
     * @param string
     */
    protected function runClone($p, $foreign, $tmplDir)
    {
        $this->addRelatedData($p);
        $p['id']    =   '__new';
        $this->id   =   '__new';
        $this->processForm('edit', $p);
    }

    /**
     * Actually Run Action: Edit
     *
     * @param array
     * @param string
     * @param string
     */
    protected function runEdit($p, $foreign, $tmplDir)
    {
        $this->addRelatedData($p);
        $tmp    =   array();
        foreach ($p as $k => $v) {
            if (is_scalar($v)) {
                $tmp[$k]    =   $v;
            }
        }

        $this->tmpl->addGlobalVars($tmp);
        $this->processForm('edit', $p);
    }

    /**
     * Actually Run Action: Delete
     *
     * @param array
     * @param string
     * @param string
     * @return bool
     */
    protected function runRm($p, $foreign, $tmplDir)
    {
        $this->tmpl->addGlobalVars($p);

        // action and id required
        if ($this->config['action'] != 'rm' || $this->id == '__new') {
            return false;
        }

        // Ask: Do you really want to delete this record?
        if ($this->req->get('force', 'no') != 'yes') {
            $this->loadTemplates('rm');
            $this->config['tmplDir']    =   $tmplDir;
            return true;
        }

        // delete for real
        $this->table->delete($this->config['table'], $this->id, $foreign);
        $this->loadTemplates('rmDeleted');
        $this->config['tmplDir']    =   $tmplDir;

        return true;
    }

    /**
     * Check Permissions
     *
     * Check required group according to action and load "anon" template
     * @return bool true if access is granted
     */
    protected function setupPermission()
    {
        if ($this->isUserInGroup($this->config['requiredgroupmanager'])) {
            $this->tmpl->addGlobalVar('user_current_requiredgroupmanager', 1);
        }
        if ($this->isUserInGroup($this->config['requiredgroup'])) {
            return true;
        }

        if (in_array($this->config['action'], array('display'))) {
            return true;
        }

        $this->loadTemplates('anon');
        return false;
    }

    /**
     * Setup Foreign Key
     *
     * Utilize user id as foreign key if user is not "requiredgroupmanager".
     *
     * @return null|string
     */
    protected function setupForeign()
    {
        // administrators may manage other's entries
        if ($this->isUserInGroup($this->config['requiredgroupmanager']) || WBContent::GROUP_ANON == $this->config['requiredgroupmanager']) {
            return null;
        }
        if (in_array($this->config['action'], array('display', 'list')) && $this->config['showsomeoneelses']) {
            return null;
        }
        if (!in_array($this->config['addcurrentuser'], array('new', 'always'))) {
            return null;
        }
        return $this->user->getId();
    }

    /**
     * Setup Sort Column
     *
     * Check config and prepare action of sort columns
     */
    protected function setupSortColumn()
    {
        // managesortcolumn is required
        if (!empty($this->config['managesortcolumn'])) {
            $this->config['level_' . $this->config['levels'] . '_checked']   =   'selected="selected" checked="checked"';
        }
        else if (in_array($this->config['action'], array('moveup', 'movedown'))) {
            $this->config['action'] =   'edit';
        }
    }

    /**
     * Setup Id
     *
     * Load record if a proper id is given
     *
     * @param string $foreign
     * @return array
     */
    protected function setupId($foreign)
    {
        // reset id
        $this->id           =   $this->config['id'];
        $this->config['id'] =   '__new';
        if (empty($this->id)) {
            $this->id   =   '__new';
        }

        // load record
        $p  =   array(array());
        if($this->id != '__new') {
            $p          =   $this->table->get($this->config['table'], $this->id, $foreign);
        }
        if (count($p) != 1) {
            $p          =   array(array());
            $this->id   =   '__new';
        }
        $p  =   $p[0];

        $this->config['id'] =   $this->id;
        $this->tmpl->addGlobalVar('id', $this->id);
        return $p;
    }

    /**
     * Setup Environment
     *
     * Configure table env and template dir. Also check required group for switching translation
     * @return string $tmplDir
     */
    protected function setupEnv()
    {
        if ($this->envOK) {
            return;
        }

        // by default, table env is the same as table
        if (empty($this->config['tableenv'])) {
            $this->config['tableenv']   =   $this->config['table'];
        }

        // patch tmplDir with table name
        $tmplDir    =   $this->config['tmplDir'];
        $this->config['tmplDir']    =   $tmplDir . '/' . $this->config['tableenv'];

        if ($this->isUserInGroup($this->config['requiredgroup'])) {
            $this->table->switchTranslation(false);
        }
        $p  =   null;
        $this->config['action']   =   strtolower($this->config['action']);

        $this->envOK    =   true;
        return $tmplDir;
    }

    /**
     * display list of records in table
     *
     * In case there is a template named "list_entry", it will be filled with paged
     * rows of table
     *
     * @param string $foreign key of user table
     */
    protected function displayList($foreign)
    {
        if (!$this->tmpl->exists('list_entry')) {
            return;
        }

        $clause     =   $this->filter->getClause();
        $options    =   $this->filter->getOptions();
        $this->tweakFilter($clause, $options);

        $this->mergeDisplayListOptions($options);

        // order
        if (empty($this->config['managesortcolumn']) && !empty($this->config['order'])) {
            $options['order']   =   array(
                                            'field' =>  $this->config['order'],
                                            'asc'   =>  1
                                        );
            if ('!' == $this->config['order'][0]) {
                $options['order']['field']  =   substr($this->config['order'], 1);
                $options['order']['asc']    =   0;
            }
        }
        $id         =   __CLASS__ . '-'  . $this->part . '-'. $this->config['table'] . '-' . $this->config['tableenv'];
        if ($this->config['limit'] && intval($this->config['limit']) > 0) {
            $options['limit']   =   intval($this->config['limit']);
        }

        $pager  =   $this->table->getPager($id, $this->config['table'], $foreign, $clause, $options);

        $this->tmpl->addGlobalVars($pager->browse($this->config['goto']), 'pager_');
        $this->tmpl->addRows('list_entry', $this->prepareListEntry($pager->get()));
    }

    /**
     * Tweak Table Filter On Demand
     *
     * Implement this method to mange with filter clause and options
     *
     * @param array
     * @param option
     */
    protected function tweakFilter(&$clause, &$option)
    {
    }

    /**
     * Prepare List Entries 4 Template
     *
     * @param array
     * @return array
     */
    protected function prepareListEntry($list)
    {
        return $list;
    }

    /**
     * Merge Options to Display List
     *
     * @param array
     */
    protected function mergeDisplayListOptions(&$options)
    {
    }

   /**
    * Save user's primary data
    *
    * Update user's forename and surname
    *
    * @param patForms $form
    * @param array $values
    */
    public function onEditValid($form, $values)
    {
        $new    =   false;
        if ('__new' == $this->id) {
            $new    =   true;
        }
        // set user id
        if (!empty($this->config['addcurrentuser'])) {
            switch ($this->config['addcurrentuser']) {
                case 'new':
                    if ($new) {
                        $values[$this->table->getIdentifier('user')]    =   $this->user->getId();
                    }
                    break;
                case 'always':
                    $values[$this->table->getIdentifier('user')]    =   $this->user->getId();
                    break;

                default:
                case 'never':
                    break;
            }
        }

        if ($this->initRelationTable()) {
            $this->relTable->extract($values);
        }

        $this->tmpl->addGlobalVars($values);

        // add value for sortcolumn
        if ($new && !empty($this->config['managesortcolumn'])) {
            $col        =   $this->config['managesortcolumn'];
            $options    =   array(
                                'limit' =>  1,
                                'order' =>  array(
                                                'field' =>  $col,
                                                'asc'   =>  0
                                            ),
                                'column'    =>  array($col)
                            );
            $clause     =   array();
            $last       =   $this->table->get($this->config['table'], null, null, $clause, $options);
            if (empty($last)) {
                $values[$col]   =   0;
            } else {
                $values[$col]   =   ++$last[0][$col];
            }
        }

        $id =   $this->saveRecord($new, $values);
        $this->tmpl->addGlobalVar('id', $id);
        return true;
    }

    /**
     * Save Primary Records in DB
     *
     * Store simple record in table
     *
     * @param bool true if database record is new
     * @param array validated form values to be saved
     * @return string id of saved record
     */
    protected function saveRecord($new, $values)
    {
        // delete old relations
        if (!$new && $this->initRelationTable()) {
            $this->relTable->delete($this->id);
        }

        $this->nlsSaver->strip($values);
        if (!$new) {
            $this->nlsSaver->save($values);
        }
        $id     =   $this->table->save($this->config['table'], $this->id, $values);
        if ($new) {
            $this->nlsSaver->setId($id);
            $this->nlsSaver->save($values);
        }

        if ($this->initRelationTable()) {
            $this->relTable->add($id);
        }
        $this->triggerEvent($id, $new, $values);
        return $id;
    }

    /**
     * Trigger Event
     *
     * Generic table editor event for each table
     *
     * @param string $id
     * @param bool $new
     * @param array $data
     */
    protected function triggerEvent($id, $new, $values)
    {
        $eData  =   array(
                    'table'     =>  $this->config['table'],
                    'id'        =>  $id,
                    'new'       =>  intval($new),
                    'save'      =>  $values
                );
        WBEvent::trigger('tableeditor:save:' . $this->config['table'], 'Saved record in table', $eData);
    }

    /**
     * Load data from related tables
     *
     * Resolve foreign keys stored in other tables and merge widata
     *
     * @param array &$data
     */
    protected function addRelatedData(&$data)
    {
        if ('__new' == $this->id) {
            return;
        }
        if (!$this->initRelationTable()) {
            return;
        }
        $this->relList  =   $this->relTable->inject($data);
    }

    /**
     * Start relation table
     *
     * If relations are configured, start relation table object
     */
    protected function initRelationTable()
    {
        if ($this->relTable) {
            return true;
        }

        // load relation config
        $config = WBClass::create('WBConfig');
        $config->load($this->getFormConfigDir(). '/edit');
        $conf   =   $config->get('relation', array());
        if (empty($conf) || !is_array($conf)) {
            return false;
        }

        $this->relTable =   WBClass::create('WBDatasource_RelationTable');
        $this->relTable->setTable($this->table);
        $this->relTable->setConfig($this->config['table'], $conf);
        return true;
    }

    /**
     * move record up in list
     *
     * Aktually swap position with next record to move current record
     * up or down.
     *
     * @param array $data record to move
     * @param bool $up move up (true) or down (false)
     */
    private function moveUp($data, $up = true)
    {
        if (empty($this->config['managesortcolumn'])) {
            return;
        }
        $col    =   $this->config['managesortcolumn'];

        // shortcut applies for moving up first row (which is on top already)
        if ($up && 1 > $data[$col]) {
            return;
        }

        $levels =   intval($this->config['levels']);
        if ($levels < 1) {
            $levels =   1;
        }

        // find other record to swap with
        $options    =   array(
                            'limit' =>  $levels,
                            'order' =>  array(
                                            'field' =>  $col,
                                            'asc'   =>  0
                                        ),
                            'column'    =>  array($col)
                        );
        $clause     =   array();
        $clause[]   =   array(
                                'field'     =>  $col,
                                'relation'  =>  'lt',
                                'value'     =>  $data[$col]
                            );

        if (!$up) {
            $clause[0]['relation']      =   'gt';
            $options['order']['asc']    =   1;
        }
        $other  =   $this->table->get($this->config['table'], null, null, $clause, $options);
        // if there is not upper entry, skip the rest
        if (empty($other)) {
            return;
        }
        // selected too many
        if (count($other) > $levels) {
            return;
        }

        // save current records in new position
        $last   =   array_pop($other);
        $save   =   array(
                        $col => $last[$col]
                        );
        $this->table->save($this->config['table'], $this->id, $save);
        $primary    =   $this->table->getIdentifier($this->config['table']);

        // move all other items to the next position
        while (0 < count($other)) {
            $id     =   $last[$primary];
            $last   =   array_pop($other);
            $save   =   array(
                            $col    =>  $last[$col]
                        );
            $this->table->save($this->config['table'], $id, $save);
        }

        // save last item in current item's previous position
        $id     =   $last[$primary];
        $save   =   array(
                            $col => $data[$col]
                        );
        $this->table->save($this->config['table'], $id, $save);
    }

   /**
    * location of form config
    *
    * Return sub directory where form element definitions are located
    *
    * @return string folder
    */
    protected function getFormConfigDir()
    {
        return 'table/' . $this->config['tableenv'];
    }

   /**
    * Fetch List of Form Elements
    *
    * Use parent's method and add translators form elements
    *
    * @use WBDatasource_NLS_Saver::addFormElements()
    * @param string name of the xml- and template-filename
    * @return array $elements
    */
    protected function getFormElementList($name)
    {
        $elements   =   parent::getFormElementList($name);
        $this->nlsSaver->addFormElements($elements);
        return $elements;
    }

   /**
    * Add form rules to just created form
    *
    *
    *
    * @param patForms $form object
    * @param string name of the xml- and template-filename
    * @param array $elements list of elements current form
    * @return bool true on success
    */
    protected function insertFormRules(&$form, $name, $elements)
    {
        return true;
    }

   /**
    * Add form to template engine
    *
    * @param patForms $form
    * @param array $values
    * @return bool true
    */
    protected function renderForm($form, $values = array())
    {
        if (!is_array($this->relList)) {
            return parent::renderForm($form, $values);
        }

        foreach ($this->relList as $k => $l) {
            $this->tmpl->addGlobalVar($k . '_list_count', count($l));
            if ($this->tmpl->exists($k .  '_list_entry')) {
                $this->tmpl->addRows($k .  '_list_entry', $l);
            }
        }

        return parent::renderForm($form, $values);
    }
}