request = $request; $this->init(); } function init() { $this->orderId = $this->request->getParam('ec_id'); $this->isGoalAnOrder = !empty($this->orderId); $this->idGoal = $this->request->getParam('idgoal'); $this->requestIsEcommerce = ($this->idGoal == 0); } function getBuyerType($existingType = GoalManager::TYPE_BUYER_NONE) { // Was there a Cart for this visit prior to the order? $this->isThereExistingCartInVisit = in_array($existingType, array(GoalManager::TYPE_BUYER_OPEN_CART, GoalManager::TYPE_BUYER_ORDERED_AND_OPEN_CART)); if (!$this->requestIsEcommerce) { return $existingType; } if ($this->isGoalAnOrder) { return self::TYPE_BUYER_ORDERED; } // request is Add to Cart if ($existingType == self::TYPE_BUYER_ORDERED || $existingType == self::TYPE_BUYER_ORDERED_AND_OPEN_CART ) { return self::TYPE_BUYER_ORDERED_AND_OPEN_CART; } return self::TYPE_BUYER_OPEN_CART; } static public function getGoalDefinitions($idSite) { $websiteAttributes = Cache::getCacheWebsiteAttributes($idSite); if (isset($websiteAttributes['goals'])) { return $websiteAttributes['goals']; } return array(); } static public function getGoalDefinition($idSite, $idGoal) { $goals = self::getGoalDefinitions($idSite); foreach ($goals as $goal) { if ($goal['idgoal'] == $idGoal) { return $goal; } } throw new Exception('Goal not found'); } static public function getGoalIds($idSite) { $goals = self::getGoalDefinitions($idSite); $goalIds = array(); foreach ($goals as $goal) { $goalIds[] = $goal['idgoal']; } return $goalIds; } /** * Look at the URL or Page Title and sees if it matches any existing Goal definition * * @param int $idSite * @param Action $action * @throws Exception * @return int Number of goals matched */ function detectGoalsMatchingUrl($idSite, $action) { if (!Common::isGoalPluginEnabled()) { return false; } $decodedActionUrl = $action->getActionUrl(); $actionType = $action->getActionType(); $goals = $this->getGoalDefinitions($idSite); foreach ($goals as $goal) { $attribute = $goal['match_attribute']; // if the attribute to match is not the type of the current action 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') ) { continue; } $url = $decodedActionUrl; // Matching on Page Title if ($attribute == 'title') { $url = $action->getActionName(); } $pattern_type = $goal['pattern_type']; $match = $this->isUrlMatchingGoal($goal, $pattern_type, $url); if ($match) { $goal['url'] = $decodedActionUrl; $this->convertedGoals[] = $goal; } } return count($this->convertedGoals) > 0; } function detectGoalId($idSite) { if (!Common::isGoalPluginEnabled()) { return false; } $goals = $this->getGoalDefinitions($idSite); if (!isset($goals[$this->idGoal])) { return false; } $goal = $goals[$this->idGoal]; $url = $this->request->getParam('url'); $goal['url'] = PageUrl::excludeQueryParametersFromUrl($url, $idSite); $goal['revenue'] = $this->getRevenue($this->request->getGoalRevenue($goal['revenue'])); $this->convertedGoals[] = $goal; return true; } /** * Records one or several goals matched in this request. * * @param int $idSite * @param array $visitorInformation * @param array $visitCustomVariables * @param Action $action */ public function recordGoals($idSite, $visitorInformation, $visitCustomVariables, $action) { $referrerTimestamp = $this->request->getParam('_refts'); $referrerUrl = $this->request->getParam('_ref'); $referrerCampaignName = trim(urldecode($this->request->getParam('_rcn'))); $referrerCampaignKeyword = trim(urldecode($this->request->getParam('_rck'))); $browserLanguage = $this->request->getBrowserLanguage(); $location_country = isset($visitorInformation['location_country']) ? $visitorInformation['location_country'] : Common::getCountry( $browserLanguage, $enableLanguageToCountryGuess = Config::getInstance()->Tracker['enable_language_to_country_guess'], $visitorInformation['location_ip'] ); $goal = array( 'idvisit' => $visitorInformation['idvisit'], 'idsite' => $idSite, 'idvisitor' => $visitorInformation['idvisitor'], 'server_time' => Tracker::getDatetimeFromTimestamp($visitorInformation['visit_last_action_time']), 'location_country' => $location_country, 'visitor_returning' => $visitorInformation['visitor_returning'], 'visitor_days_since_first' => $visitorInformation['visitor_days_since_first'], 'visitor_days_since_order' => $visitorInformation['visitor_days_since_order'], 'visitor_count_visits' => $visitorInformation['visitor_count_visits'], ); $extraLocationCols = array('location_region', 'location_city', 'location_latitude', 'location_longitude'); foreach ($extraLocationCols as $col) { if (isset($visitorInformation[$col])) { $goal[$col] = $visitorInformation[$col]; } } // Copy Custom Variables from Visit row to the Goal conversion // Otherwise, set the Custom Variables found in the cookie sent with this request $goal += $visitCustomVariables; $maxCustomVariables = CustomVariables::getMaxCustomVariables(); 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]; } } // Attributing the correct Referrer to this conversion. // Priority order is as follows: // 0) In some cases, the campaign is not passed from the JS so we look it up from the current visit // 1) Campaign name/kwd parsed in the JS // 2) Referrer URL stored in the _ref cookie // 3) If no info from the cookie, attribute to the current visit referrer // 3) Default values: current referrer $type = $visitorInformation['referer_type']; $name = $visitorInformation['referer_name']; $keyword = $visitorInformation['referer_keyword']; $time = $visitorInformation['visit_first_action_time']; // 0) In some (unknown!?) cases the campaign is not found in the attribution cookie, but the URL ref was found. // In this case we look up if the current visit is credited to a campaign and will credit this campaign rather than the URL ref (since campaigns have higher priority) if (empty($referrerCampaignName) && $type == Common::REFERRER_TYPE_CAMPAIGN && !empty($name) ) { // Use default values per above } // 1) Campaigns from 1st party cookie elseif (!empty($referrerCampaignName)) { $type = Common::REFERRER_TYPE_CAMPAIGN; $name = $referrerCampaignName; $keyword = $referrerCampaignKeyword; $time = $referrerTimestamp; } // 2) Referrer URL parsing elseif (!empty($referrerUrl)) { $referrer = new Referrer(); $referrer = $referrer->getReferrerInformation($referrerUrl, $currentUrl = '', $idSite); // if the parsed referrer is interesting enough, ie. website or search engine if (in_array($referrer['referer_type'], array(Common::REFERRER_TYPE_SEARCH_ENGINE, Common::REFERRER_TYPE_WEBSITE))) { $type = $referrer['referer_type']; $name = $referrer['referer_name']; $keyword = $referrer['referer_keyword']; $time = $referrerTimestamp; } } $this->setCampaignValuesToLowercase($type, $name, $keyword); $goal += array( 'referer_type' => $type, 'referer_name' => $name, 'referer_keyword' => $keyword, // this field is currently unused 'referer_visit_server_date' => date("Y-m-d", $time), ); // some goals are converted, so must be ecommerce Order or Cart Update if ($this->requestIsEcommerce) { $this->recordEcommerceGoal($goal, $visitorInformation); } else { $this->recordStandardGoals($goal, $action, $visitorInformation); } } /** * Returns rounded decimal revenue, or if revenue is integer, then returns as is. * * @param int|float $revenue * @return int|float */ protected function getRevenue($revenue) { if (round($revenue) == $revenue) { return $revenue; } return round($revenue, self::REVENUE_PRECISION); } /** * Records an Ecommerce conversion in the DB. Deals with Items found in the request. * Will deal with 2 types of conversions: Ecommerce Order and Ecommerce Cart update (Add to cart, Update Cart etc). * * @param array $conversion * @param array $visitInformation */ protected function recordEcommerceGoal($conversion, $visitInformation) { if ($this->isThereExistingCartInVisit) { Common::printDebug("There is an existing cart for this visit"); } if ($this->isGoalAnOrder) { $conversion['idgoal'] = self::IDGOAL_ORDER; $conversion['idorder'] = $this->orderId; $conversion['buster'] = Common::hashStringToInt($this->orderId); $conversion['revenue_subtotal'] = $this->getRevenue($this->request->getParam('ec_st')); $conversion['revenue_tax'] = $this->getRevenue($this->request->getParam('ec_tx')); $conversion['revenue_shipping'] = $this->getRevenue($this->request->getParam('ec_sh')); $conversion['revenue_discount'] = $this->getRevenue($this->request->getParam('ec_dt')); $debugMessage = 'The conversion is an Ecommerce order'; } // If Cart update, select current items in the previous Cart else { $conversion['buster'] = 0; $conversion['idgoal'] = self::IDGOAL_CART; $debugMessage = 'The conversion is an Ecommerce Cart Update'; } $conversion['revenue'] = $this->getRevenue($this->request->getGoalRevenue($defaultRevenue = 0)); Common::printDebug($debugMessage . ':' . var_export($conversion, true)); // INSERT or Sync items in the Cart / Order for this visit & order $items = $this->getEcommerceItemsFromRequest(); if ($items === false) { return; } $itemsCount = 0; foreach ($items as $item) { $itemsCount += $item[self::INTERNAL_ITEM_QUANTITY]; } $conversion['items'] = $itemsCount; if($this->isThereExistingCartInVisit) { $updateWhere = array( 'idvisit' => $visitInformation['idvisit'], 'idgoal' => self::IDGOAL_CART, 'buster' => 0, ); $recorded = $this->updateExistingConversion($conversion, $updateWhere); } else { $recorded = $this->insertNewConversion($conversion, $visitInformation); } if ($recorded) { $this->recordEcommerceItems($conversion, $items, $visitInformation); } /** * Triggered after successfully persisting an ecommerce conversion. * * _Note: Subscribers should be wary of doing any expensive computation here as it may slow * the tracker down._ * * @param array $conversion The conversion entity that was just persisted. See what information * it contains [here](/guides/persistence-and-the-mysql-backend#conversions). * @param array $visitInformation The visit entity that we are tracking a conversion for. See what * information it contains [here](/guides/persistence-and-the-mysql-backend#visits). */ Piwik::postEvent('Tracker.recordEcommerceGoal', array($conversion, $visitInformation)); } /** * Returns Items read from the request string * @return array|bool */ protected function getEcommerceItemsFromRequest() { $items = Common::unsanitizeInputValue($this->request->getParam('ec_items')); if (empty($items)) { Common::printDebug("There are no Ecommerce items in the request"); // we still record an Ecommerce order without any item in it return array(); } $items = Common::json_decode($items, $assoc = true); if (!is_array($items)) { Common::printDebug("Error while json_decode the Ecommerce items = " . var_export($items, true)); return false; } $cleanedItems = $this->getCleanedEcommerceItems($items); return $cleanedItems; } /** * Loads the Ecommerce items from the request and records them in the DB * * @param array $goal * @param array $items * @throws Exception * @return int Number of items in the cart */ protected function recordEcommerceItems($goal, $items) { $itemInCartBySku = array(); foreach ($items as $item) { $itemInCartBySku[$item[0]] = $item; } // Select all items currently in the Cart if any $sql = "SELECT idaction_sku, idaction_name, idaction_category, idaction_category2, idaction_category3, idaction_category4, idaction_category5, price, quantity, deleted, idorder as idorder_original_value FROM " . Common::prefixTable('log_conversion_item') . " WHERE idvisit = ? AND (idorder = ? OR idorder = ?)"; $bind = array($goal['idvisit'], isset($goal['idorder']) ? $goal['idorder'] : self::ITEM_IDORDER_ABANDONED_CART, self::ITEM_IDORDER_ABANDONED_CART ); $itemsInDb = Tracker::getDatabase()->fetchAll($sql, $bind); Common::printDebug("Items found in current cart, for conversion_item (visit,idorder)=" . var_export($bind, true)); Common::printDebug($itemsInDb); // Look at which items need to be deleted, which need to be added or updated, based on the SKU $skuFoundInDb = $itemsToUpdate = array(); foreach ($itemsInDb as $itemInDb) { $skuFoundInDb[] = $itemInDb['idaction_sku']; // Ensure price comparisons will have the same assumption $itemInDb['price'] = $this->getRevenue($itemInDb['price']); $itemInDbOriginal = $itemInDb; $itemInDb = array_values($itemInDb); // Cast all as string, because what comes out of the fetchAll() are strings $itemInDb = $this->getItemRowCast($itemInDb); //Item in the cart in the DB, but not anymore in the cart 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; } // Items to UPDATE $this->updateEcommerceItems($goal, $itemsToUpdate); // Items to INSERT $itemsToInsert = array(); foreach ($items as $item) { if (!in_array($item[0], $skuFoundInDb)) { $itemsToInsert[] = $item; } } $this->insertEcommerceItems($goal, $itemsToInsert); } // In the GET items parameter, each item has the following array of information const INDEX_ITEM_SKU = 0; const INDEX_ITEM_NAME = 1; const INDEX_ITEM_CATEGORY = 2; const INDEX_ITEM_PRICE = 3; const INDEX_ITEM_QUANTITY = 4; // Used in the array of items, internally to this class const INTERNAL_ITEM_SKU = 0; const INTERNAL_ITEM_NAME = 1; const INTERNAL_ITEM_CATEGORY = 2; const INTERNAL_ITEM_CATEGORY2 = 3; const INTERNAL_ITEM_CATEGORY3 = 4; const INTERNAL_ITEM_CATEGORY4 = 5; const INTERNAL_ITEM_CATEGORY5 = 6; const INTERNAL_ITEM_PRICE = 7; const INTERNAL_ITEM_QUANTITY = 8; /** * Reads items from the request, then looks up the names from the lookup table * and returns a clean array of items ready for the database. * * @param array $items * @return array $cleanedItems */ protected function getCleanedEcommerceItems($items) { // Clean up the items array $cleanedItems = array(); foreach ($items as $item) { $name = $category = $category2 = $category3 = $category4 = $category5 = false; $price = 0; $quantity = 1; // items are passed in the request as an array: ( $sku, $name, $category, $price, $quantity ) 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]; } // self::INDEX_ITEM_* are in order $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 ); } // Lookup Item SKUs, Names & Categories Ids $actionsToLookupAllItems = array(); // Each item has 7 potential "ids" to lookup in the lookup table $columnsInEachRow = 1 + 1 + self::MAXIMUM_PRODUCT_CATEGORIES; foreach ($cleanedItems as $item) { $actionsToLookup = array(); list($sku, $name, $category, $price, $quantity) = $item; $actionsToLookup[] = array(trim($sku), Action::TYPE_ECOMMERCE_ITEM_SKU); $actionsToLookup[] = array(trim($name), Action::TYPE_ECOMMERCE_ITEM_NAME); // Only one category if (!is_array($category)) { $actionsToLookup[] = array(trim($category), Action::TYPE_ECOMMERCE_ITEM_CATEGORY); } // Multiple categories 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); } } // Ensure that each row has the same number of columns, fill in the blanks for ($i = count($actionsToLookup); $i < $columnsInEachRow; $i++) { $actionsToLookup[] = array(false, Action::TYPE_ECOMMERCE_ITEM_CATEGORY); } $actionsToLookupAllItems = array_merge($actionsToLookupAllItems, $actionsToLookup); } $actionsLookedUp = TableLogAction::loadIdsAction($actionsToLookupAllItems); // Replace SKU, name & category by their ID action foreach ($cleanedItems as $index => &$item) { // SKU $item[0] = $actionsLookedUp[$index * $columnsInEachRow + 0]; // Name $item[1] = $actionsLookedUp[$index * $columnsInEachRow + 1]; // Categories $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; } /** * Updates the cart items in the DB * that have been modified since the last cart update * * @param array $goal * @param array $itemsToUpdate * * @return void */ 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); $updateParts = $sqlBind = array(); foreach ($newRow AS $name => $value) { $updateParts[] = $name . " = ?"; $sqlBind[] = $value; } $sql = 'UPDATE ' . Common::prefixTable('log_conversion_item') . " SET " . implode($updateParts, ', ') . " WHERE idvisit = ? AND idorder = ? AND idaction_sku = ?"; $sqlBind[] = $newRow['idvisit']; $sqlBind[] = $item['idorder_original_value']; $sqlBind[] = $newRow['idaction_sku']; Tracker::getDatabase()->query($sql, $sqlBind); } } /** * Inserts in the cart in the DB the new items * that were not previously in the cart * * @param array $goal * @param array $itemsToInsert * * @return void */ protected function insertEcommerceItems($goal, $itemsToInsert) { if (empty($itemsToInsert)) { return; } Common::printDebug("Ecommerce items that are added to the cart/order"); Common::printDebug($itemsToInsert); $sql = "INSERT INTO " . Common::prefixTable('log_conversion_item') . " (idaction_sku, idaction_name, idaction_category, idaction_category2, idaction_category3, idaction_category4, idaction_category5, price, quantity, deleted, idorder, idsite, idvisitor, server_time, idvisit) VALUES "; $i = 0; $bind = array(); foreach ($itemsToInsert as $item) { if ($i > 0) { $sql .= ','; } $newRow = array_values($this->getItemRowEnriched($goal, $item)); $sql .= " ( " . Common::getSqlStringFieldsArray($newRow) . " ) "; $i++; $bind = array_merge($bind, $newRow); } Tracker::getDatabase()->query($sql, $bind); Common::printDebug($sql); Common::printDebug($bind); } 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' => $item[self::INTERNAL_ITEM_PRICE], 'quantity' => $item[self::INTERNAL_ITEM_QUANTITY], 'deleted' => isset($item['deleted']) ? $item['deleted'] : 0, //deleted 'idorder' => isset($goal['idorder']) ? $goal['idorder'] : self::ITEM_IDORDER_ABANDONED_CART, //idorder = 0 in log_conversion_item for carts 'idsite' => $goal['idsite'], 'idvisitor' => $goal['idvisitor'], 'server_time' => $goal['server_time'], 'idvisit' => $goal['idvisit'] ); return $newRow; } /** * Records a standard non-Ecommerce goal in the DB (URL/Title matching), * linking the conversion to the action that triggered it * @param $goal * @param Action $action * @param $visitorInformation */ protected function recordStandardGoals($goal, $action, $visitorInformation) { foreach ($this->convertedGoals as $convertedGoal) { Common::printDebug("- Goal " . $convertedGoal['idgoal'] . " matched. Recording..."); $conversion = $goal; $conversion['idgoal'] = $convertedGoal['idgoal']; $conversion['url'] = $convertedGoal['url']; $conversion['revenue'] = $this->getRevenue($convertedGoal['revenue']); if (!is_null($action)) { $conversion['idaction_url'] = $action->getIdActionUrl(); $conversion['idlink_va'] = $action->getIdLinkVisitAction(); } // If multiple Goal conversions per visit, set a cache buster $conversion['buster'] = $convertedGoal['allow_multiple'] == 0 ? '0' : $visitorInformation['visit_last_action_time']; $this->insertNewConversion($conversion, $visitorInformation); /** * Triggered after successfully recording a non-ecommerce conversion. * * _Note: Subscribers should be wary of doing any expensive computation here as it may slow * the tracker down._ * * @param array $conversion The conversion entity that was just persisted. See what information * it contains [here](/guides/persistence-and-the-mysql-backend#conversions). */ Piwik::postEvent('Tracker.recordStandardGoals', array($conversion)); } } /** * Helper function used by other record* methods which will INSERT or UPDATE the conversion in the DB * * @param array $conversion * @param array $visitInformation * @return bool */ protected function insertNewConversion($conversion, $visitInformation) { /** * Triggered before persisting a new [conversion entity](/guides/persistence-and-the-mysql-backend#conversions). * * This event can be used to modify conversion information or to add new information to be persisted. * * @param array $conversion The conversion entity. Read [this](/guides/persistence-and-the-mysql-backend#conversions) * to see what it contains. * @param array $visitInformation The visit entity that we are tracking a conversion for. See what * information it contains [here](/guides/persistence-and-the-mysql-backend#visits). * @param \Piwik\Tracker\Request $request An object describing the tracking request being processed. */ Piwik::postEvent('Tracker.newConversionInformation', array(&$conversion, $visitInformation, $this->request)); $newGoalDebug = $conversion; $newGoalDebug['idvisitor'] = bin2hex($newGoalDebug['idvisitor']); Common::printDebug($newGoalDebug); $fields = implode(", ", array_keys($conversion)); $bindFields = Common::getSqlStringFieldsArray($conversion); $sql = 'INSERT IGNORE INTO ' . Common::prefixTable('log_conversion') . " ($fields) VALUES ($bindFields) "; $bind = array_values($conversion); $result = Tracker::getDatabase()->query($sql, $bind); // If a record was inserted, we return true return Tracker::getDatabase()->rowCount($result) > 0; } /** * Casts the item array so that array comparisons work nicely * @param array $row * @return array */ 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 updateExistingConversion($newGoal, $updateWhere) { $updateParts = $sqlBind = $updateWhereParts = array(); foreach ($newGoal AS $name => $value) { $updateParts[] = $name . " = ?"; $sqlBind[] = $value; } foreach ($updateWhere as $name => $value) { $updateWhereParts[] = $name . " = ?"; $sqlBind[] = $value; } $sql = 'UPDATE ' . Common::prefixTable('log_conversion') . " SET " . implode($updateParts, ', ') . " WHERE " . implode($updateWhereParts, ' AND '); try { Tracker::getDatabase()->query($sql, $sqlBind); } catch(Exception $e){ Common::printDebug("There was an error while updating the Conversion: " . $e->getMessage()); return false; } return true; } /** * @param $type * @param $name * @param $keyword */ protected function setCampaignValuesToLowercase($type, &$name, &$keyword) { if ($type === Common::REFERRER_TYPE_CAMPAIGN) { if (!empty($name)) { $name = Common::mb_strtolower($name); } if (!empty($keyword)) { $keyword = Common::mb_strtolower($keyword); } } } /** * @param $goal * @param $pattern_type * @param $url * @return bool * @throws \Exception */ protected function isUrlMatchingGoal($goal, $pattern_type, $url) { switch ($pattern_type) { case 'regex': $pattern = $goal['pattern']; if (strpos($pattern, '/') !== false && strpos($pattern, '\\/') === false ) { $pattern = str_replace('/', '\\/', $pattern); } $pattern = '/' . $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: throw new Exception(Piwik::translate('General_ExceptionInvalidGoalPattern', array($pattern_type))); break; } return $match; } }