| <?php |
|
|
| |
| |
| |
| |
| |
| |
|
|
| namespace Piwik\Tracker; |
|
|
| use Exception; |
| use Piwik\Common; |
| use Piwik\Container\StaticContainer; |
| use Piwik\Date; |
| use Piwik\Exception\InvalidRequestParameterException; |
| use Piwik\Log\LoggerInterface; |
| use Piwik\Piwik; |
| use Piwik\Plugin\Dimension\ConversionDimension; |
| use Piwik\Plugin\Dimension\VisitDimension; |
| use Piwik\Plugin\Manager; |
| use Piwik\Plugins\CustomVariables\CustomVariables; |
| use Piwik\Plugins\Events\Actions\ActionEvent; |
| use Piwik\Tracker\Visit\VisitProperties; |
|
|
| class GoalManager |
| { |
| |
| public const TYPE_BUYER_OPEN_CART = 2; |
| public const TYPE_BUYER_ORDERED_AND_OPEN_CART = 3; |
|
|
| |
| public const ITEM_IDORDER_ABANDONED_CART = 0; |
|
|
| |
| public const IDGOAL_CART = -1; |
| public const IDGOAL_ORDER = 0; |
|
|
| public const REVENUE_PRECISION = 2; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public const MAX_ALLOWED_REVENUE = 1000000000000; |
|
|
| public const MAXIMUM_PRODUCT_CATEGORIES = 5; |
|
|
| |
| public const INDEX_ITEM_SKU = 0; |
| public const INDEX_ITEM_NAME = 1; |
| public const INDEX_ITEM_CATEGORY = 2; |
| public const INDEX_ITEM_PRICE = 3; |
| public const INDEX_ITEM_QUANTITY = 4; |
|
|
| |
| public const INTERNAL_ITEM_SKU = 0; |
| public const INTERNAL_ITEM_NAME = 1; |
| public const INTERNAL_ITEM_CATEGORY = 2; |
| public const INTERNAL_ITEM_CATEGORY2 = 3; |
| public const INTERNAL_ITEM_CATEGORY3 = 4; |
| public const INTERNAL_ITEM_CATEGORY4 = 5; |
| public const INTERNAL_ITEM_CATEGORY5 = 6; |
| public const INTERNAL_ITEM_PRICE = 7; |
| public const INTERNAL_ITEM_QUANTITY = 8; |
|
|
| public static $NUMERIC_MATCH_ATTRIBUTES = [ |
| 'visit_duration', |
| ]; |
|
|
| |
| |
| |
| |
| |
| private $currentGoal = array(); |
|
|
| public function detectIsThereExistingCartInVisit($visitInformation) |
| { |
| if (empty($visitInformation['visit_goal_buyer'])) { |
| return false; |
| } |
|
|
| $goalBuyer = $visitInformation['visit_goal_buyer']; |
| $types = array(GoalManager::TYPE_BUYER_OPEN_CART, GoalManager::TYPE_BUYER_ORDERED_AND_OPEN_CART); |
|
|
| |
| return in_array($goalBuyer, $types); |
| } |
|
|
| public static function getGoalDefinitions($idSite) |
| { |
| $websiteAttributes = Cache::getCacheWebsiteAttributes($idSite); |
|
|
| if (isset($websiteAttributes['goals'])) { |
| return $websiteAttributes['goals']; |
| } |
|
|
| return array(); |
| } |
|
|
| public static function getGoalDefinition($idSite, $idGoal) |
| { |
| $goals = self::getGoalDefinitions($idSite); |
|
|
| foreach ($goals as $goal) { |
| if ($goal['idgoal'] == $idGoal) { |
| return $goal; |
| } |
| } |
|
|
| throw new Exception('Goal not found'); |
| } |
|
|
| public static function getGoalIds($idSite) |
| { |
| $goals = self::getGoalDefinitions($idSite); |
| $goalIds = array(); |
|
|
| foreach ($goals as $goal) { |
| $goalIds[] = $goal['idgoal']; |
| } |
|
|
| return $goalIds; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| public function detectGoalsMatchingUrl($idSite, $action, VisitProperties $visitor, Request $request) |
| { |
| if (!Common::isGoalPluginEnabled()) { |
| return array(); |
| } |
|
|
| $goals = $this->getGoalDefinitions($idSite); |
|
|
| $convertedGoals = array(); |
| foreach ($goals as $goal) { |
| $convertedUrl = $this->detectGoalMatch($goal, $action, $visitor, $request); |
| if (!is_null($convertedUrl)) { |
| $convertedGoals[] = array('url' => $convertedUrl) + $goal; |
| } |
| } |
| return $convertedGoals; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| public function detectGoalMatch($goal, Action $action, VisitProperties $visitor, Request $request) |
| { |
| $actionType = $action->getActionType(); |
|
|
| $attribute = $goal['match_attribute']; |
|
|
| |
| if (in_array($attribute, self::$NUMERIC_MATCH_ATTRIBUTES)) { |
| return $this->detectNumericGoalMatch($goal, $action, $visitor, $request); |
| } |
|
|
| |
| if ( |
| (($attribute == 'url' || $attribute == 'title') && $actionType != Action::TYPE_PAGE_URL) |
| || ($attribute == 'file' && $actionType != Action::TYPE_DOWNLOAD) |
| || ($attribute == 'external_website' && $actionType != Action::TYPE_OUTLINK) |
| || ($attribute == 'manually') |
| || self::isEventMatchingGoal($goal) && $actionType != Action::TYPE_EVENT |
| ) { |
| return null; |
| } |
|
|
| switch ($attribute) { |
| case 'title': |
| |
| $actionToMatch = $action->getActionName(); |
| break; |
| case 'event_action': |
| $actionToMatch = $action->getEventAction(); |
| break; |
| case 'event_name': |
| $actionToMatch = $action->getEventName(); |
| break; |
| case 'event_category': |
| $actionToMatch = $action->getEventCategory(); |
| break; |
| |
| default: |
| $actionToMatch = $action->getActionUrlRaw(); |
| break; |
| } |
|
|
| $pattern_type = $goal['pattern_type']; |
|
|
| $match = $this->isUrlMatchingGoal($goal, $pattern_type, $actionToMatch); |
| if (!$match) { |
| return null; |
| } |
|
|
| return $action->getActionUrl(); |
| } |
|
|
| private function detectNumericGoalMatch($goal, Action $action, VisitProperties $visitProperties, Request $request) |
| { |
| switch ($goal['match_attribute']) { |
| case 'visit_duration': |
| $firstActionTime = $visitProperties->getProperty('visit_first_action_time'); |
| if (empty($firstActionTime)) { |
| return null; |
| } |
|
|
| $visitDurationInSecs = $request->getCurrentTimestamp() - ((int) $firstActionTime); |
| $valueToMatchAgainst = $visitDurationInSecs / 60; |
| break; |
| default: |
| return null; |
| } |
|
|
| $pattern = (float) $goal['pattern']; |
|
|
| Common::printDebug("Matching {$goal['match_attribute']} (current value = $valueToMatchAgainst, idGoal = {$goal['idgoal']}) {$goal['pattern_type']} $pattern."); |
|
|
| switch ($goal['pattern_type']) { |
| case 'greater_than': |
| $matches = $valueToMatchAgainst > $pattern; |
| break; |
| default: |
| return null; |
| } |
|
|
| if ($matches) { |
| Common::printDebug("Conversion detected for idGoal = , idGoal = {$goal['idgoal']}."); |
| return $action->getActionUrl(); |
| } else { |
| return null; |
| } |
| } |
|
|
| public function detectGoalId($idSite, Request $request) |
| { |
| if (!Common::isGoalPluginEnabled()) { |
| return null; |
| } |
|
|
| $idGoal = $request->getParam('idgoal'); |
|
|
| $goals = $this->getGoalDefinitions($idSite); |
|
|
| if (!isset($goals[$idGoal])) { |
| throw new InvalidRequestParameterException('idGoal ' . $idGoal . ' does not exist'); |
| } |
|
|
| $goal = $goals[$idGoal]; |
|
|
| $url = $request->getParam('url'); |
| $goal['url'] = PageUrl::excludeQueryParametersFromUrl($url, $idSite); |
| return $goal; |
| } |
|
|
| |
| |
| |
| public function recordGoals(VisitProperties $visitProperties, Request $request) |
| { |
| $visitorInformation = $visitProperties->getProperties(); |
|
|
| |
| $action = $request->getMetadata('Actions', 'action'); |
|
|
| $goal = $this->getGoalFromVisitor($visitProperties, $request, $action); |
|
|
| if (Manager::getInstance()->isPluginActivated('CustomVariables')) { |
| |
| |
| |
| $visitCustomVariables = $request->getMetadata('CustomVariables', 'visitCustomVariables') ?: array(); |
| $goal += $visitCustomVariables; |
| $maxCustomVariables = CustomVariables::getNumUsableCustomVariables(); |
|
|
| for ($i = 1; $i <= $maxCustomVariables; $i++) { |
| if ( |
| isset($visitorInformation['custom_var_k' . $i]) |
| && strlen($visitorInformation['custom_var_k' . $i]) |
| ) { |
| $goal['custom_var_k' . $i] = $visitorInformation['custom_var_k' . $i]; |
| } |
| if ( |
| isset($visitorInformation['custom_var_v' . $i]) |
| && strlen($visitorInformation['custom_var_v' . $i]) |
| ) { |
| $goal['custom_var_v' . $i] = $visitorInformation['custom_var_v' . $i]; |
| } |
| } |
| } |
|
|
| |
| $isRequestEcommerce = $request->getMetadata('Ecommerce', 'isRequestEcommerce'); |
| if ($isRequestEcommerce) { |
| $this->recordEcommerceGoal($visitProperties, $request, $goal, $action); |
| } else { |
| $this->recordStandardGoals($visitProperties, $request, $goal, $action); |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| protected function getRevenue($revenue) |
| { |
| |
| if (abs((float) $revenue) > self::MAX_ALLOWED_REVENUE) { |
| StaticContainer::get(LoggerInterface::class)->debug( |
| "Ecommerce value ({$revenue}) exceeds the allowed maximum of " . self::MAX_ALLOWED_REVENUE . " and was rejected (treated as no revenue)." |
| ); |
|
|
| return 0; |
| } |
|
|
| if (round($revenue) != $revenue) { |
| $revenue = round($revenue, self::REVENUE_PRECISION); |
| } |
|
|
| $revenue = Common::forceDotAsSeparatorForDecimalPoint($revenue); |
|
|
| return $revenue; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| protected function recordEcommerceGoal(VisitProperties $visitProperties, Request $request, $conversion, $action) |
| { |
| $isThereExistingCartInVisit = $request->getMetadata('Goals', 'isThereExistingCartInVisit'); |
| if ($isThereExistingCartInVisit) { |
| Common::printDebug("There is an existing cart for this visit"); |
| } |
|
|
| $visitor = Visitor::makeFromVisitProperties($visitProperties, $request); |
|
|
| $isGoalAnOrder = $request->getMetadata('Ecommerce', 'isGoalAnOrder'); |
| if ($isGoalAnOrder) { |
| $debugMessage = 'The conversion is an Ecommerce order'; |
|
|
| $orderId = $request->getParam('ec_id'); |
|
|
| $conversion['idorder'] = $orderId; |
| $conversion['idgoal'] = self::IDGOAL_ORDER; |
| $conversion['buster'] = Common::hashStringToInt($orderId); |
|
|
| $conversionDimensions = ConversionDimension::getAllDimensions(); |
| $conversion = $this->triggerHookOnDimensions($request, $conversionDimensions, 'onEcommerceOrderConversion', $visitor, $action, $conversion); |
| } else { |
| |
| $debugMessage = 'The conversion is an Ecommerce Cart Update'; |
|
|
| $conversion['buster'] = 0; |
| $conversion['idgoal'] = self::IDGOAL_CART; |
|
|
| $conversionDimensions = ConversionDimension::getAllDimensions(); |
| $conversion = $this->triggerHookOnDimensions($request, $conversionDimensions, 'onEcommerceCartUpdateConversion', $visitor, $action, $conversion); |
| } |
|
|
| Common::printDebug($debugMessage . ':' . var_export($conversion, true)); |
|
|
| |
| $items = $this->getEcommerceItemsFromRequest($request); |
|
|
| if (false === $items) { |
| return; |
| } |
|
|
| $itemsCount = 0; |
| foreach ($items as $item) { |
| $itemsCount += $item[GoalManager::INTERNAL_ITEM_QUANTITY]; |
| } |
|
|
| $conversion['items'] = $itemsCount; |
|
|
| if ($isThereExistingCartInVisit) { |
| $recorded = $this->getModel()->updateConversion( |
| $visitProperties->getProperty('idvisit'), |
| self::IDGOAL_CART, |
| $conversion |
| ); |
| } else { |
| $recorded = $this->insertNewConversion($conversion, $visitProperties->getProperties(), $request, $action); |
| } |
|
|
| if ($recorded) { |
| $this->recordEcommerceItems($conversion, $items); |
| } |
| } |
|
|
| |
| |
| |
| |
| private function getEcommerceItemsFromRequest(Request $request) |
| { |
| $items = $request->getParam('ec_items'); |
|
|
| if (empty($items)) { |
| Common::printDebug("There are no Ecommerce items in the request"); |
| |
| return array(); |
| } |
|
|
| if (!is_array($items)) { |
| Common::printDebug("Error while json_decode the Ecommerce items = " . var_export($items, true)); |
| return false; |
| } |
|
|
| $items = Common::unsanitizeInputValues($items); |
|
|
| $cleanedItems = $this->getCleanedEcommerceItems($items); |
| return $cleanedItems; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| protected function recordEcommerceItems($goal, $items) |
| { |
| $itemInCartBySku = array(); |
| foreach ($items as $item) { |
| $itemInCartBySku[$item[0]] = $item; |
| } |
|
|
| $itemsInDb = $this->getModel()->getAllItemsCurrentlyInTheCart($goal, self::ITEM_IDORDER_ABANDONED_CART); |
|
|
| |
| $skuFoundInDb = $itemsToUpdate = array(); |
|
|
| foreach ($itemsInDb as $itemInDb) { |
| $skuFoundInDb[] = $itemInDb['idaction_sku']; |
|
|
| |
| $itemInDb['price'] = $this->getRevenue($itemInDb['price']); |
| $itemInDbOriginal = $itemInDb; |
| $itemInDb = array_values($itemInDb); |
|
|
| |
| $itemInDb = $this->getItemRowCast($itemInDb); |
|
|
| |
| if (!isset($itemInCartBySku[$itemInDb[0]])) { |
| $itemToUpdate = array_merge( |
| $itemInDb, |
| array('deleted' => 1, |
| 'idorder_original_value' => $itemInDbOriginal['idorder_original_value'], |
| ) |
| ); |
|
|
| $itemsToUpdate[] = $itemToUpdate; |
| Common::printDebug("Item found in the previous Cart, but no in the current cart/order"); |
| Common::printDebug($itemToUpdate); |
| continue; |
| } |
|
|
| $newItem = $itemInCartBySku[$itemInDb[0]]; |
| $newItem = $this->getItemRowCast($newItem); |
|
|
| if (count($itemInDb) != count($newItem)) { |
| Common::printDebug("ERROR: Different format in items from cart and DB"); |
| throw new Exception(" Item in DB and Item in cart have a different format, this is not expected... " . var_export($itemInDb, true) . var_export($newItem, true)); |
| } |
| Common::printDebug("Item has changed since the last cart. Previous item stored in cart in database:"); |
| Common::printDebug($itemInDb); |
| Common::printDebug("New item to UPDATE the previous row:"); |
| $newItem['idorder_original_value'] = $itemInDbOriginal['idorder_original_value']; |
| Common::printDebug($newItem); |
| $itemsToUpdate[] = $newItem; |
| } |
|
|
| |
| $this->updateEcommerceItems($goal, $itemsToUpdate); |
|
|
| |
| $itemsToInsert = array(); |
| foreach ($items as $item) { |
| if (!in_array($item[0], $skuFoundInDb)) { |
| $itemsToInsert[] = $item; |
| } |
| } |
|
|
| $this->insertEcommerceItems($goal, $itemsToInsert); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| private function getCleanedEcommerceItems($items) |
| { |
| |
| $cleanedItems = array(); |
| foreach ($items as $item) { |
| $name = $category = $category2 = $category3 = $category4 = $category5 = false; |
| $price = 0; |
| $quantity = 1; |
|
|
| |
| if (empty($item[self::INDEX_ITEM_SKU])) { |
| continue; |
| } |
|
|
| $sku = $item[self::INDEX_ITEM_SKU]; |
| if (!empty($item[self::INDEX_ITEM_NAME])) { |
| $name = $item[self::INDEX_ITEM_NAME]; |
| } |
|
|
| if (!empty($item[self::INDEX_ITEM_CATEGORY])) { |
| $category = $item[self::INDEX_ITEM_CATEGORY]; |
| } |
|
|
| if ( |
| isset($item[self::INDEX_ITEM_PRICE]) |
| && is_numeric($item[self::INDEX_ITEM_PRICE]) |
| ) { |
| $price = $this->getRevenue($item[self::INDEX_ITEM_PRICE]); |
| } |
| if ( |
| !empty($item[self::INDEX_ITEM_QUANTITY]) |
| && is_numeric($item[self::INDEX_ITEM_QUANTITY]) |
| ) { |
| $quantity = (int)$item[self::INDEX_ITEM_QUANTITY]; |
| } |
|
|
| |
| $cleanedItems[] = array( |
| self::INTERNAL_ITEM_SKU => $sku, |
| self::INTERNAL_ITEM_NAME => $name, |
| self::INTERNAL_ITEM_CATEGORY => $category, |
| self::INTERNAL_ITEM_CATEGORY2 => $category2, |
| self::INTERNAL_ITEM_CATEGORY3 => $category3, |
| self::INTERNAL_ITEM_CATEGORY4 => $category4, |
| self::INTERNAL_ITEM_CATEGORY5 => $category5, |
| self::INTERNAL_ITEM_PRICE => $price, |
| self::INTERNAL_ITEM_QUANTITY => $quantity, |
| ); |
| } |
|
|
| |
| $actionsToLookupAllItems = array(); |
|
|
| |
| $columnsInEachRow = 1 + 1 + self::MAXIMUM_PRODUCT_CATEGORIES; |
|
|
| foreach ($cleanedItems as $item) { |
| $actionsToLookup = array(); |
| [$sku_check, $name_check, $category, $price, $quantity] = $item; |
| $sku = is_array($sku_check) ? join(',', $sku_check) : $sku_check; |
| $actionsToLookup[] = array(trim($sku), Action::TYPE_ECOMMERCE_ITEM_SKU); |
| $name = is_array($name_check) ? join(',', $name_check) : $name_check; |
| $actionsToLookup[] = array(trim($name), Action::TYPE_ECOMMERCE_ITEM_NAME); |
|
|
| |
| if (!is_array($category)) { |
| $actionsToLookup[] = array(trim($category), Action::TYPE_ECOMMERCE_ITEM_CATEGORY); |
| } else { |
| |
| $countCategories = 0; |
| foreach ($category as $productCategory) { |
| $productCategory = trim($productCategory); |
| if (empty($productCategory)) { |
| continue; |
| } |
| $countCategories++; |
| if ($countCategories > self::MAXIMUM_PRODUCT_CATEGORIES) { |
| break; |
| } |
| $actionsToLookup[] = array($productCategory, Action::TYPE_ECOMMERCE_ITEM_CATEGORY); |
| } |
| } |
| |
| for ($i = count($actionsToLookup); $i < $columnsInEachRow; $i++) { |
| $actionsToLookup[] = array(false, Action::TYPE_ECOMMERCE_ITEM_CATEGORY); |
| } |
| $actionsToLookupAllItems = array_merge($actionsToLookupAllItems, $actionsToLookup); |
| } |
|
|
| $actionsLookedUp = TableLogAction::loadIdsAction($actionsToLookupAllItems); |
|
|
| |
| foreach ($cleanedItems as $index => &$item) { |
| |
| $item[0] = $actionsLookedUp[$index * $columnsInEachRow + 0]; |
| |
| $item[1] = $actionsLookedUp[$index * $columnsInEachRow + 1]; |
| |
| $item[2] = $actionsLookedUp[$index * $columnsInEachRow + 2]; |
| $item[3] = $actionsLookedUp[$index * $columnsInEachRow + 3]; |
| $item[4] = $actionsLookedUp[$index * $columnsInEachRow + 4]; |
| $item[5] = $actionsLookedUp[$index * $columnsInEachRow + 5]; |
| $item[6] = $actionsLookedUp[$index * $columnsInEachRow + 6]; |
| } |
|
|
| return $cleanedItems; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| protected function updateEcommerceItems($goal, $itemsToUpdate) |
| { |
| if (empty($itemsToUpdate)) { |
| return; |
| } |
|
|
| Common::printDebug("Goal data used to update ecommerce items:"); |
| Common::printDebug($goal); |
|
|
| foreach ($itemsToUpdate as $item) { |
| $newRow = $this->getItemRowEnriched($goal, $item); |
| Common::printDebug($newRow); |
|
|
| $this->getModel()->updateEcommerceItem($item['idorder_original_value'], $newRow); |
| } |
| } |
|
|
| private function getModel() |
| { |
| return new Model(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| protected function insertEcommerceItems($goal, $itemsToInsert) |
| { |
| if (empty($itemsToInsert)) { |
| return; |
| } |
|
|
| Common::printDebug("Ecommerce items that are added to the cart/order"); |
| Common::printDebug($itemsToInsert); |
|
|
| $items = array(); |
|
|
| foreach ($itemsToInsert as $item) { |
| $items[] = $this->getItemRowEnriched($goal, $item); |
| } |
|
|
| $this->getModel()->createEcommerceItems($items); |
| } |
|
|
| protected function getItemRowEnriched($goal, $item) |
| { |
| $newRow = array( |
| 'idaction_sku' => (int)$item[self::INTERNAL_ITEM_SKU], |
| 'idaction_name' => (int)$item[self::INTERNAL_ITEM_NAME], |
| 'idaction_category' => (int)$item[self::INTERNAL_ITEM_CATEGORY], |
| 'idaction_category2' => (int)$item[self::INTERNAL_ITEM_CATEGORY2], |
| 'idaction_category3' => (int)$item[self::INTERNAL_ITEM_CATEGORY3], |
| 'idaction_category4' => (int)$item[self::INTERNAL_ITEM_CATEGORY4], |
| 'idaction_category5' => (int)$item[self::INTERNAL_ITEM_CATEGORY5], |
| 'price' => Common::forceDotAsSeparatorForDecimalPoint($item[self::INTERNAL_ITEM_PRICE]), |
| 'quantity' => $item[self::INTERNAL_ITEM_QUANTITY], |
| 'deleted' => isset($item['deleted']) ? $item['deleted'] : 0, |
| 'idorder' => isset($goal['idorder']) ? $goal['idorder'] : self::ITEM_IDORDER_ABANDONED_CART, |
| 'idsite' => $goal['idsite'], |
| 'idvisitor' => $goal['idvisitor'], |
| 'server_time' => $goal['server_time'], |
| 'idvisit' => $goal['idvisit'], |
| ); |
| return $newRow; |
| } |
|
|
| public function getGoalColumn($column) |
| { |
| if (array_key_exists($column, $this->currentGoal)) { |
| return $this->currentGoal[$column]; |
| } |
|
|
| return false; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| protected function recordStandardGoals(VisitProperties $visitProperties, Request $request, $goal, $action) |
| { |
| $visitor = Visitor::makeFromVisitProperties($visitProperties, $request); |
|
|
| $convertedGoals = $request->getMetadata('Goals', 'goalsConverted') ?: array(); |
| foreach ($convertedGoals as $convertedGoal) { |
| $this->currentGoal = $convertedGoal; |
| Common::printDebug("- Goal " . $convertedGoal['idgoal'] . " matched. Recording..."); |
| $conversion = $goal; |
| $conversion['idgoal'] = $convertedGoal['idgoal']; |
| $conversion['url'] = $convertedGoal['url']; |
|
|
| if (!is_null($action)) { |
| $conversion['idaction_url'] = $action->getIdActionUrl(); |
| $conversion['idlink_va'] = $action->getIdLinkVisitAction(); |
| } |
|
|
| |
| if ($convertedGoal['allow_multiple'] == 0) { |
| $conversion['buster'] = 0; |
| } else { |
| $lastActionTime = $visitProperties->getProperty('visit_last_action_time'); |
| if (empty($lastActionTime)) { |
| $conversion['buster'] = $this->makeRandomMySqlUnsignedInt(10); |
| } else { |
| $conversion['buster'] = $this->makeRandomMySqlUnsignedInt(2) . mb_substr($visitProperties->getProperty('visit_last_action_time'), 2); |
| } |
| } |
|
|
| $conversionDimensions = ConversionDimension::getAllDimensions(); |
| $conversion = $this->triggerHookOnDimensions($request, $conversionDimensions, 'onGoalConversion', $visitor, $action, $conversion); |
|
|
| $this->insertNewConversion($conversion, $visitProperties->getProperties(), $request, $action, $convertedGoal); |
| } |
| } |
|
|
| private function makeRandomMySqlUnsignedInt($length) |
| { |
| |
| $randomInt = Common::getRandomString(1, '123'); |
| $randomInt .= Common::getRandomString($length - 1, '0123456789'); |
| return $randomInt; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| protected function insertNewConversion($conversion, $visitInformation, Request $request, $action, $convertedGoal = null) |
| { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| Piwik::postEvent('Tracker.newConversionInformation', array(&$conversion, $visitInformation, $request, $action)); |
|
|
| if ( |
| !empty($convertedGoal) |
| && $this->isEventMatchingGoal($convertedGoal) |
| && !empty($convertedGoal['event_value_as_revenue']) |
| ) { |
| $eventValue = ActionEvent::getEventValue($request); |
| if ($eventValue != '') { |
| $conversion['revenue'] = $eventValue; |
| } |
| } |
|
|
| $newGoalDebug = $conversion; |
| $newGoalDebug['idvisitor'] = bin2hex($newGoalDebug['idvisitor']); |
| Common::printDebug($newGoalDebug); |
|
|
| $idorder = $request->getParam('ec_id'); |
|
|
| $wasInserted = $this->getModel()->createConversion($conversion); |
| if ( |
| !$wasInserted |
| ) { |
| if (!empty($idorder)) { |
| $idSite = $request->getIdSite(); |
| throw new InvalidRequestParameterException("Invalid non-unique idsite/idorder combination ($idSite, $idorder), conversion was not inserted."); |
| } elseif ($conversion['buster'] > 0) { |
| |
| |
| StaticContainer::get(LoggerInterface::class)->warning("Failed to insert goal due to duplicate idvisit/idgoal/buster combination ({$conversion['idvisit']}, {$conversion['idgoal']}, {$conversion['buster']})"); |
| } |
| } |
|
|
| return $wasInserted; |
| } |
|
|
| |
| |
| |
| |
| |
| protected function getItemRowCast($row) |
| { |
| return array( |
| (string)(int)$row[self::INTERNAL_ITEM_SKU], |
| (string)(int)$row[self::INTERNAL_ITEM_NAME], |
| (string)(int)$row[self::INTERNAL_ITEM_CATEGORY], |
| (string)(int)$row[self::INTERNAL_ITEM_CATEGORY2], |
| (string)(int)$row[self::INTERNAL_ITEM_CATEGORY3], |
| (string)(int)$row[self::INTERNAL_ITEM_CATEGORY4], |
| (string)(int)$row[self::INTERNAL_ITEM_CATEGORY5], |
| (string)$row[self::INTERNAL_ITEM_PRICE], |
| (string)$row[self::INTERNAL_ITEM_QUANTITY], |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| protected function isUrlMatchingGoal($goal, $pattern_type, $url) |
| { |
| $url = Common::unsanitizeInputValue($url); |
| $goal['pattern'] = Common::unsanitizeInputValue($goal['pattern']); |
|
|
| $match = $this->isGoalPatternMatchingUrl($goal, $pattern_type, $url); |
|
|
| if (!$match) { |
| |
| $goal['pattern'] = urldecode($goal['pattern']); |
|
|
| $match = $this->isGoalPatternMatchingUrl($goal, $pattern_type, $url); |
| } |
| return $match; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| private function triggerHookOnDimensions(Request $request, $dimensions, $hook, $visitor, $action, $valuesToUpdate) |
| { |
| foreach ($dimensions as $dimension) { |
| $value = $dimension->$hook($request, $visitor, $action, $this); |
|
|
| if (false !== $value) { |
| if (is_float($value)) { |
| $value = Common::forceDotAsSeparatorForDecimalPoint($value); |
| } |
|
|
| $fieldName = $dimension->getColumnName(); |
| $visitor->setVisitorColumn($fieldName, $value); |
|
|
| $valuesToUpdate[$fieldName] = $value; |
| } |
| } |
|
|
| return $valuesToUpdate; |
| } |
|
|
| private function getGoalFromVisitor(VisitProperties $visitProperties, Request $request, $action) |
| { |
| $lastVisitTime = $visitProperties->getProperty('visit_last_action_time'); |
| if (!$lastVisitTime) { |
| $lastVisitTime = $request->getCurrentTimestamp(); |
| } |
|
|
| if (!empty($lastVisitTime) && is_numeric($lastVisitTime)) { |
| |
| |
| |
| $lastVisitTime = Date::getDatetimeFromTimestamp($lastVisitTime); |
| } |
|
|
| $goal = array( |
| 'idvisit' => $visitProperties->getProperty('idvisit'), |
| 'idvisitor' => $visitProperties->getProperty('idvisitor'), |
| 'server_time' => $lastVisitTime, |
| ); |
|
|
| $visitDimensions = VisitDimension::getAllDimensions(); |
|
|
| $visit = Visitor::makeFromVisitProperties($visitProperties, $request); |
| foreach ($visitDimensions as $dimension) { |
| $value = $dimension->onAnyGoalConversion($request, $visit, $action); |
| if (false !== $value) { |
| $goal[$dimension->getColumnName()] = $value; |
| } |
| } |
|
|
| return $goal; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| protected function isGoalPatternMatchingUrl($goal, $pattern_type, $url) |
| { |
| switch ($pattern_type) { |
| case 'regex': |
| $pattern = self::formatRegex($goal['pattern']); |
| if (!$goal['case_sensitive']) { |
| $pattern .= 'i'; |
| } |
| $match = (@preg_match($pattern, $url) == 1); |
| break; |
| case 'contains': |
| if ($goal['case_sensitive']) { |
| $matched = strpos($url, $goal['pattern']); |
| } else { |
| $matched = stripos($url, $goal['pattern']); |
| } |
| $match = ($matched !== false); |
| break; |
| case 'exact': |
| if ($goal['case_sensitive']) { |
| $matched = strcmp($goal['pattern'], $url); |
| } else { |
| $matched = strcasecmp($goal['pattern'], $url); |
| } |
| $match = ($matched == 0); |
| break; |
| default: |
| try { |
| StaticContainer::get(LoggerInterface::class)->warning(Piwik::translate('General_ExceptionInvalidGoalPattern', array($pattern_type))); |
| } catch (\Exception $e) { |
| } |
| $match = false; |
| break; |
| } |
| return $match; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| public static function formatRegex($pattern) |
| { |
| if ( |
| strpos($pattern, '/') !== false |
| && strpos($pattern, '\\/') === false |
| ) { |
| $pattern = str_replace('/', '\\/', $pattern); |
| } |
| return '/' . $pattern . '/'; |
| } |
|
|
| public static function isEventMatchingGoal($goal) |
| { |
| return in_array($goal['match_attribute'], array('event_action', 'event_name', 'event_category')); |
| } |
| } |
|
|