<?php
/**
 * Piwik - Open source web analytics
 *
 * @link http://piwik.org
 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 *
 */
namespace Piwik\Plugins\Goals;

use Exception;
use Piwik\API\Request;
use Piwik\Common;
use Piwik\DataTable\Filter\AddColumnsProcessedMetricsGoal;
use Piwik\DataTable;
use Piwik\FrontController;
use Piwik\Piwik;
use Piwik\Plugins\Referrers\API as APIReferrers;
use Piwik\View\ReportsByDimension;
use Piwik\View;
use Piwik\ViewDataTable\Factory;

/**
 *
 */
class Controller extends \Piwik\Plugin\Controller
{
    const CONVERSION_RATE_PRECISION = 1;

    /**
     * Number of "Your top converting keywords/etc are" to display in the per Goal overview page
     * @var int
     */
    const COUNT_TOP_ROWS_TO_DISPLAY = 3;

    const ECOMMERCE_LOG_SHOW_ORDERS = 1;
    const ECOMMERCE_LOG_SHOW_ABANDONED_CARTS = 2;

    protected $goalColumnNameToLabel = array(
        'avg_order_revenue' => 'General_AverageOrderValue',
        'nb_conversions'    => 'Goals_ColumnConversions',
        'conversion_rate'   => 'General_ColumnConversionRate',
        'revenue'           => 'General_TotalRevenue',
        'items'             => 'General_PurchasedProducts',
    );

    private function formatConversionRate($conversionRate)
    {
        if ($conversionRate instanceof DataTable) {
            if ($conversionRate->getRowsCount() == 0) {
                $conversionRate = 0;
            } else {
                $columns = $conversionRate->getFirstRow()->getColumns();
                $conversionRate = (float)reset($columns);
            }
        }
        return sprintf('%.' . self::CONVERSION_RATE_PRECISION . 'f%%', $conversionRate);
    }

    public function __construct()
    {
        parent::__construct();
        $this->idSite = Common::getRequestVar('idSite', null, 'int');
        $this->goals = API::getInstance()->getGoals($this->idSite);
        foreach ($this->goals as &$goal) {
            $goal['name'] = Common::sanitizeInputValue($goal['name']);
            if (isset($goal['pattern'])) {
                $goal['pattern'] = Common::sanitizeInputValue($goal['pattern']);
            }
        }
    }

    public function widgetGoalReport()
    {
        $view = $this->getGoalReportView($idGoal = Common::getRequestVar('idGoal', null, 'string'));
        $view->displayFullReport = false;
        return $view->render();
    }

    public function goalReport()
    {
        $view = $this->getGoalReportView($idGoal = Common::getRequestVar('idGoal', null, 'string'));
        $view->displayFullReport = true;
        return $view->render();
    }

    public function ecommerceReport()
    {
        if (!\Piwik\Plugin\Manager::getInstance()->isPluginActivated('CustomVariables')) {
            throw new Exception("Ecommerce Tracking requires that the plugin Custom Variables is enabled. Please enable the plugin CustomVariables (or ask your admin).");
        }

        $view = $this->getGoalReportView($idGoal = Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER);
        $view->displayFullReport = true;
        return $view->render();
    }

    public function getEcommerceLog($fetch = false)
    {
        $saveGET = $_GET;
        $filterEcommerce = Common::getRequestVar('filterEcommerce', self::ECOMMERCE_LOG_SHOW_ORDERS, 'int');
        if($filterEcommerce == self::ECOMMERCE_LOG_SHOW_ORDERS) {
            $segment = urlencode('visitEcommerceStatus==ordered,visitEcommerceStatus==orderedThenAbandonedCart');
        } else {
            $segment = urlencode('visitEcommerceStatus==abandonedCart,visitEcommerceStatus==orderedThenAbandonedCart');
        }
        $_GET['segment'] = $segment;
        $_GET['filterEcommerce'] = $filterEcommerce;
        $_GET['widget'] = 1;
        $output = FrontController::getInstance()->dispatch('Live', 'getVisitorLog', array($fetch));
        $_GET = $saveGET;
        return $output;
    }

    protected function getGoalReportView($idGoal = false)
    {
        $view = new View('@Goals/getGoalReportView');
        if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
            $goalDefinition['name'] = Piwik::translate('Goals_Ecommerce');
            $goalDefinition['allow_multiple'] = true;
            $ecommerce = $view->ecommerce = true;
        } else {
            if (!isset($this->goals[$idGoal])) {
                Piwik::redirectToModule('Goals', 'index', array('idGoal' => null));
            }
            $goalDefinition = $this->goals[$idGoal];
        }
        $this->setGeneralVariablesView($view);
        $goal = $this->getMetricsForGoal($idGoal);
        foreach ($goal as $name => $value) {
            $view->$name = $value;
        }
        if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
            $goal = $this->getMetricsForGoal(Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART);
            foreach ($goal as $name => $value) {
                $name = 'cart_' . $name;
                $view->$name = $value;
            }
        }
        $view->idGoal = $idGoal;
        $view->goalName = $goalDefinition['name'];
        $view->goalAllowMultipleConversionsPerVisit = $goalDefinition['allow_multiple'];
        $view->graphEvolution = $this->getEvolutionGraph(array('nb_conversions'), $idGoal);
        $view->nameGraphEvolution = 'Goals.getEvolutionGraph' . $idGoal;
        $view->topDimensions = $this->getTopDimensions($idGoal);

        // conversion rate for new and returning visitors
        $segment = urldecode(\Piwik\Plugins\VisitFrequency\API::RETURNING_VISITOR_SEGMENT);
        $conversionRateReturning = API::getInstance()->getConversionRate($this->idSite, Common::getRequestVar('period'), Common::getRequestVar('date'), $segment, $idGoal);
        $view->conversion_rate_returning = $this->formatConversionRate($conversionRateReturning);
        $segment = 'visitorType==new';
        $conversionRateNew = API::getInstance()->getConversionRate($this->idSite, Common::getRequestVar('period'), Common::getRequestVar('date'), $segment, $idGoal);
        $view->conversion_rate_new = $this->formatConversionRate($conversionRateNew);
        $view->goalReportsByDimension = $this->getGoalReportsByDimensionTable(
            $view->nb_conversions, isset($ecommerce), !empty($view->cart_nb_conversions));
        return $view;
    }

    public function index()
    {
        $view = $this->getOverviewView();

        // unsanitize goal names and other text data (not done in API so as not to break
        // any other code/cause security issues)
        $goals = $this->goals;
        foreach ($goals as &$goal) {
            $goal['name'] = Common::unsanitizeInputValue($goal['name']);
            if (isset($goal['pattern'])) {
                $goal['pattern'] = Common::unsanitizeInputValue($goal['pattern']);
            }
        }
        $view->goalsJSON = Common::json_encode($goals);

        $view->userCanEditGoals = Piwik::isUserHasAdminAccess($this->idSite);
        $view->ecommerceEnabled = $this->site->isEcommerceEnabled();
        $view->displayFullReport = true;
        return $view->render();
    }

    public function widgetGoalsOverview()
    {
        $view = $this->getOverviewView();
        $view->displayFullReport = false;
        return $view->render();
    }

    protected function getOverviewView()
    {
        $view = new View('@Goals/getOverviewView');
        $this->setGeneralVariablesView($view);

        $view->graphEvolution = $this->getEvolutionGraph(array('nb_conversions'));
        $view->nameGraphEvolution = 'GoalsgetEvolutionGraph';

        // sparkline for the historical data of the above values
        $view->urlSparklineConversions = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_conversions'), 'idGoal' => ''));
        $view->urlSparklineConversionRate = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('conversion_rate'), 'idGoal' => ''));
        $view->urlSparklineRevenue = $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('revenue'), 'idGoal' => ''));

        // Pass empty idGoal will return Goal overview
        $request = new Request("method=Goals.get&format=original&idGoal=");
        $datatable = $request->process();
        $dataRow = $datatable->getFirstRow();
        $view->nb_conversions = $dataRow->getColumn('nb_conversions');
        $view->nb_visits_converted = $dataRow->getColumn('nb_visits_converted');
        $view->conversion_rate = $this->formatConversionRate($dataRow->getColumn('conversion_rate'));
        $view->revenue = $dataRow->getColumn('revenue');

        $goalMetrics = array();
        foreach ($this->goals as $idGoal => $goal) {
            $goalMetrics[$idGoal] = $this->getMetricsForGoal($idGoal);
            $goalMetrics[$idGoal]['name'] = $goal['name'];
            $goalMetrics[$idGoal]['goalAllowMultipleConversionsPerVisit'] = $goal['allow_multiple'];
        }

        $view->goalMetrics = $goalMetrics;
        $view->goals = $this->goals;
        $view->goalReportsByDimension = $this->getGoalReportsByDimensionTable(
            $view->nb_conversions, $ecommerce = false, !empty($view->cart_nb_conversions));
        return $view;
    }

    public function getLastNbConversionsGraph()
    {
        $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getConversions');
        return $this->renderView($view);
    }

    public function getLastConversionRateGraph()
    {
        $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getConversionRate');
        return $this->renderView($view);
    }

    public function getLastRevenueGraph()
    {
        $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.getRevenue');
        return $this->renderView($view);
    }

    public function addNewGoal()
    {
        $view = new View('@Goals/addNewGoal');
        $this->setGeneralVariablesView($view);
        $view->userCanEditGoals = Piwik::isUserHasAdminAccess($this->idSite);
        $view->onlyShowAddNewGoal = true;
        return $view->render();
    }

    public function getEvolutionGraph(array $columns = array(), $idGoal = false)
    {
        if (empty($columns)) {
            $columns = Common::getRequestVar('columns');
            $columns = Piwik::getArrayFromApiParameter($columns);
        }

        $columns = !is_array($columns) ? array($columns) : $columns;

        if (empty($idGoal)) {
            $idGoal = Common::getRequestVar('idGoal', false, 'string');
        }
        $view = $this->getLastUnitGraph($this->pluginName, __FUNCTION__, 'Goals.get');
        $view->requestConfig->request_parameters_to_modify['idGoal'] = $idGoal;

        $nameToLabel = $this->goalColumnNameToLabel;
        if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
            $nameToLabel['nb_conversions'] = 'General_EcommerceOrders';
        } elseif ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_CART) {
            $nameToLabel['nb_conversions'] = Piwik::translate('General_VisitsWith', Piwik::translate('Goals_AbandonedCart'));
            $nameToLabel['conversion_rate'] = $nameToLabel['nb_conversions'];
            $nameToLabel['revenue'] = Piwik::translate('Goals_LeftInCart', Piwik::translate('General_ColumnRevenue'));
            $nameToLabel['items'] = Piwik::translate('Goals_LeftInCart', Piwik::translate('Goals_Products'));
        }

        $selectableColumns = array('nb_conversions', 'conversion_rate', 'revenue');
        if ($this->site->isEcommerceEnabled()) {
            $selectableColumns[] = 'items';
            $selectableColumns[] = 'avg_order_revenue';
        }

        foreach (array_merge($columns, $selectableColumns) as $columnName) {
            $columnTranslation = '';
            // find the right translation for this column, eg. find 'revenue' if column is Goal_1_revenue
            foreach ($nameToLabel as $metric => $metricTranslation) {
                if (strpos($columnName, $metric) !== false) {
                    $columnTranslation = Piwik::translate($metricTranslation);
                    break;
                }
            }

            if (!empty($idGoal) && isset($this->goals[$idGoal])) {
                $goalName = $this->goals[$idGoal]['name'];
                $columnTranslation = "$columnTranslation (" . Piwik::translate('Goals_GoalX', "$goalName") . ")";
            }
            $view->config->translations[$columnName] = $columnTranslation;
        }
        $view->config->columns_to_display = $columns;
        $view->config->selectable_columns = $selectableColumns;

        $langString = $idGoal ? 'Goals_SingleGoalOverviewDocumentation' : 'Goals_GoalsOverviewDocumentation';
        $view->config->documentation = Piwik::translate($langString, '<br />');

        return $this->renderView($view);
    }

    protected function getTopDimensions($idGoal)
    {
        $columnNbConversions = 'goal_' . $idGoal . '_nb_conversions';
        $columnConversionRate = 'goal_' . $idGoal . '_conversion_rate';

        $topDimensionsToLoad = array();

        if (\Piwik\Plugin\Manager::getInstance()->isPluginActivated('UserCountry')) {
            $topDimensionsToLoad += array(
                'country' => 'UserCountry.getCountry',
            );
        }

        $keywordNotDefinedString = '';
        if (\Piwik\Plugin\Manager::getInstance()->isPluginActivated('Referrers')) {
            $keywordNotDefinedString = APIReferrers::getKeywordNotDefinedString();
            $topDimensionsToLoad += array(
                'keyword' => 'Referrers.getKeywords',
                'website' => 'Referrers.getWebsites',
            );
        }
        $topDimensions = array();
        foreach ($topDimensionsToLoad as $dimensionName => $apiMethod) {
            $request = new Request("method=$apiMethod
								&format=original
								&filter_update_columns_when_show_all_goals=1
								&idGoal=" . AddColumnsProcessedMetricsGoal::GOALS_FULL_TABLE . "
								&filter_sort_order=desc
								&filter_sort_column=$columnNbConversions" .
                // select a couple more in case some are not valid (ie. conversions==0 or they are "Keyword not defined")
                "&filter_limit=" . (self::COUNT_TOP_ROWS_TO_DISPLAY + 2));
            $datatable = $request->process();
            $topDimension = array();
            $count = 0;
            foreach ($datatable->getRows() as $row) {
                $conversions = $row->getColumn($columnNbConversions);
                if ($conversions > 0
                    && $count < self::COUNT_TOP_ROWS_TO_DISPLAY

                    // Don't put the "Keyword not defined" in the best segment since it's irritating
                    && !($dimensionName == 'keyword'
                        && $row->getColumn('label') == $keywordNotDefinedString)
                ) {
                    $topDimension[] = array(
                        'name'            => $row->getColumn('label'),
                        'nb_conversions'  => $conversions,
                        'conversion_rate' => $this->formatConversionRate($row->getColumn($columnConversionRate)),
                        'metadata'        => $row->getMetadata(),
                    );
                    $count++;
                }
            }
            $topDimensions[$dimensionName] = $topDimension;
        }
        return $topDimensions;
    }

    protected function getMetricsForGoal($idGoal)
    {
        $request = new Request("method=Goals.get&format=original&idGoal=$idGoal");
        $datatable = $request->process();
        $dataRow = $datatable->getFirstRow();
        $nbConversions = $dataRow->getColumn('nb_conversions');
        $nbVisitsConverted = $dataRow->getColumn('nb_visits_converted');
        // Backward compatibilty before 1.3, this value was not processed
        if (empty($nbVisitsConverted)) {
            $nbVisitsConverted = $nbConversions;
        }
        $revenue = $dataRow->getColumn('revenue');
        $return = array(
            'id'                         => $idGoal,
            'nb_conversions'             => (int)$nbConversions,
            'nb_visits_converted'        => (int)$nbVisitsConverted,
            'conversion_rate'            => $this->formatConversionRate($dataRow->getColumn('conversion_rate')),
            'revenue'                    => $revenue ? $revenue : 0,
            'urlSparklineConversions'    => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('nb_conversions'), 'idGoal' => $idGoal)),
            'urlSparklineConversionRate' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('conversion_rate'), 'idGoal' => $idGoal)),
            'urlSparklineRevenue'        => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('revenue'), 'idGoal' => $idGoal)),
        );
        if ($idGoal == Piwik::LABEL_ID_GOAL_IS_ECOMMERCE_ORDER) {
            $items = $dataRow->getColumn('items');
            $aov = $dataRow->getColumn('avg_order_revenue');
            $return = array_merge($return, array(
                                                'revenue_subtotal'              => $dataRow->getColumn('revenue_subtotal'),
                                                'revenue_tax'                   => $dataRow->getColumn('revenue_tax'),
                                                'revenue_shipping'              => $dataRow->getColumn('revenue_shipping'),
                                                'revenue_discount'              => $dataRow->getColumn('revenue_discount'),

                                                'items'                         => $items ? $items : 0,
                                                'avg_order_revenue'             => $aov ? $aov : 0,
                                                'urlSparklinePurchasedProducts' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('items'), 'idGoal' => $idGoal)),
                                                'urlSparklineAverageOrderValue' => $this->getUrlSparkline('getEvolutionGraph', array('columns' => array('avg_order_revenue'), 'idGoal' => $idGoal)),
                                           ));
        }
        return $return;
    }

    /**
     * Utility function that returns HTML that displays Goal information for reports. This
     * is the HTML that is at the bottom of every goals page.
     *
     * @param int $conversions The number of conversions for this goal (or all goals
     *                         in case of the overview).
     * @param bool $ecommerce Whether to show ecommerce reports or not.
     * @param bool $cartNbConversions Whether there are cart conversions or not for this
     *                                goal.
     * @return string
     */
    private function getGoalReportsByDimensionTable($conversions, $ecommerce = false, $cartNbConversions = false)
    {
        $preloadAbandonedCart = $cartNbConversions !== false && $conversions == 0;

        $goalReportsByDimension = new ReportsByDimension('Goals');

        // add ecommerce reports
        $ecommerceCustomParams = array();
        if ($ecommerce) {
            if ($preloadAbandonedCart) {
                $ecommerceCustomParams['viewDataTable'] = 'ecommerceAbandonedCart';
                $ecommerceCustomParams['filterEcommerce'] = self::ECOMMERCE_LOG_SHOW_ABANDONED_CARTS;
            }

            $goalReportsByDimension->addReport(
                'Goals_EcommerceReports', 'Goals_ProductSKU', 'Goals.getItemsSku', $ecommerceCustomParams);
            $goalReportsByDimension->addReport(
                'Goals_EcommerceReports', 'Goals_ProductName', 'Goals.getItemsName', $ecommerceCustomParams);
            $goalReportsByDimension->addReport(
                'Goals_EcommerceReports', 'Goals_ProductCategory', 'Goals.getItemsCategory', $ecommerceCustomParams);

            $goalReportsByDimension->addReport(
                'Goals_EcommerceReports', 'Goals_EcommerceLog', 'Goals.getEcommerceLog', $ecommerceCustomParams);
        }

        if ($conversions > 0) {
            // for non-Goals reports, we show the goals table
            $customParams = $ecommerceCustomParams + array('documentationForGoalsPage' => '1');

            if (Common::getRequestVar('idGoal', '') === '') // if no idGoal, use 0 for overview
            {
                $customParams['idGoal'] = '0'; // NOTE: Must be string! Otherwise Piwik_View_HtmlTable_Goals fails.
            }

            $allReports = Goals::getReportsWithGoalMetrics();
            foreach ($allReports as $category => $reports) {
                $categoryText = Piwik::translate('Goals_ViewGoalsBy', $category);
                foreach ($reports as $report) {
                    if(empty($report['viewDataTable'])) {
                        $report['viewDataTable'] = 'tableGoals';
                    }
                    $customParams['viewDataTable'] = $report['viewDataTable'];

                    $goalReportsByDimension->addReport(
                        $categoryText, $report['name'], $report['module'] . '.' . $report['action'], $customParams);
                }
            }
        }

        return $goalReportsByDimension->render();
    }

    //
    // Report rendering actions
    //

    public function getItemsSku()
    {
        return $this->renderReport(__FUNCTION__);
    }

    public function getItemsName()
    {
        return $this->renderReport(__FUNCTION__);
    }

    public function getItemsCategory()
    {
        return $this->renderReport(__FUNCTION__);
    }

    public function getVisitsUntilConversion()
    {
        return $this->renderReport(__FUNCTION__);
    }

    public function getDaysToConversion()
    {
        return $this->renderReport(__FUNCTION__);
    }
}
