<?php
/**
 * Filter Component
 * Author: Danilo Vukotic, ddaffy@gmail.com
 * 2010-2011
 *
// TODO: customizable class/id prefix for filter element
// TODO: make it more ajax-friendly
// TODO: multiple filters on the same page (how to differentiate them automatically)
// TODO: handle date(time) fields

// possibly done, lost track of it.. :)
// TODO: non-ambiguity problem with conditions - enable field names that include model name
 */

class FilterComponent extends Object {

    var $controller = null;
    var $settings = array();
    var $enabled = false;

    function initialize(&$controller, $settings = array()) {

        $action = $controller->action;
        $this->enabled = false;

        foreach($settings as $actions => $config) {

            if (strpos(",{$actions},", ",{$action},") !== false) {

                $this->enabled = true;

                // perform some special operations on settings array (because this cannot be done when
                // statically defining $settings array - no functions allowed)
                foreach($settings[$actions] as $field => &$options) {

                    $default_filter_options = array('fields' => array($field), 'operator' => '=', 'encoded' => true);
                    $options['filter_options'] = array_merge($default_filter_options, (!empty($options['filter_options']) ? $options['filter_options'] : array()));

                    // translate input labels (stupid)
                    if (!empty($options['form_options']['label']))
                        $options['form_options']['label'] = __($options['form_options']['label'], true);

                    // if $options['form_options']['options'] is string, then it will be taken for
                    // function name, and options filled with return value of specified funcion
                    // TODO: add parameter support for options_function.
                    if (!empty($options['form_options']['options'])) {
                        if (empty($options['form_options']['empty'])) $options['form_options']['empty'] = Configure::read('Formats.form_helper_empty_option');

                        if (is_string($options['form_options']['options'])) {
                            $options_function = "_fa{$options['form_options']['options']}";
                            if (method_exists($controller, $options_function)) {
                                $options['form_options']['options'] = $controller->$options_function();
                            } elseif (Configure::read($options['form_options']['options'])) {
                                $options['form_options']['options'] = Configure::read($options['form_options']['options']);
                            } else {
                                $options['form_options']['options'] = array();
                            }
                        }
                    }
                }

                $this->settings = $settings[$actions];
                break;
            }
        }

        $this->controller =& $controller;
        $this->controller->filterConditions = array();
        return $this->enabled;
    }

    function startup(&$controller) {
        if (!$this->enabled) return false;

        // TODO: make it work with multiple models defined for pagination
        // TODO: make the way Component returns conditions more configurable (return to pagination, or variable, or..)
	    if ($this->_initFilter()) {    // avoid entering buildConditions if all filter fields' values are empty
	        $conds = $this->buildConditions();
            $this->controller->filterConditions = $conds;
	        $controller->paginate['conditions'] = $conds;
	    }
    }

    function _initFilter() {

        $any_value_set = false;

        if (!empty($this->settings)) {

            // if filter form is submitted, build filter url and redirect
    	    if (!empty($this->controller->data['Filter'])) {
    	        $params = array();
    	        foreach($this->settings as $filter => &$options) {
    	            if (isset($this->controller->data['Filter'][$filter])) {
    	                $options['form_options']['value'] = $this->controller->data['Filter'][$filter];
    	                $params[$filter] = ($options['filter_options']['encoded'] ? base64_url_encode($this->controller->data['Filter'][$filter]) : $this->controller->data['Filter'][$filter]);
    	            }
    	        }
    	        if (!empty($params)) {
    	            $url = array('plugin' => $this->controller->params['plugin'], 'controller' => $this->controller->params['controller'], 'action' => $this->controller->params['action']);
    	            $url = array_merge($params, $url);

    	            // redirect won't do us any good if we're doing ajax request, so we just bypass it
    	            // (this is not perfect, no persistance in pagination links and stuff..)
    	            if ($this->controller->RequestHandler->isAjax()) {
    	                $this->controller->params['named'] = $params;
    	            } else {
    	                $this->controller->redirect($url);
    	            }
    	        }
    	    }

    	    // if filter params are passed in url, decode them and feed to form
			$this->controller->filterParams = array();
    	    if (!empty($this->controller->params['named'])) {
    	        foreach($this->settings as $filter => &$options) {
    	            if (!empty($this->controller->params['named'][$filter])) {
    	                $options['form_options']['value'] = ($options['filter_options']['encoded'] ? base64_url_decode($this->controller->params['named'][$filter]) : $this->controller->params['named'][$filter]);
    	                $any_value_set = true;

						// store key-value filter for convenience, so we can easily determine
						// from controller what filters are enabled
						$this->controller->filterParams[$filter] = $options['form_options']['value'];
    	            }
    	        }
    	    }
        }

        $this->controller->set('filter', $this->settings);    // for use in view
        return $any_value_set;
    }

    // builds conditions array from filter
    function buildConditions() {

        $conditions = array();

        foreach($this->settings as $filter => $options) {
            if (isset($options['form_options']['value'])) {

                $v = $options['form_options']['value'];
                $filter_options = $options['filter_options'];

                $c = array();

                foreach($filter_options['fields'] as $field) {
                    switch($filter_options['operator']) {
                        default:
                        case '=': $c[] = array($field => $v); break;
                        case '<': $c[] = array("{$field} <" => $v); break;
                        case '>': $c[] = array("{$field} >" => $v); break;
                        case '<=': $c[] = array("{$field} <=" => $v); break;
                        case '>=': $c[] = array("{$field} >=" => $v); break;
                        case 'like^': $c[] = array("{$field} LIKE" => "{$v}%"); break;
                        case 'like$': $c[] = array("{$field} LIKE" => "%{$v}"); break;
                        case 'like': $c[] = array("{$field} LIKE" => "%{$v}%"); break;
                    }
                }
                if (count($c) > 1) $c = array('or' => $c);
                $conditions[] = $c;
            }
        }

        return $conditions;
    }

}