diff options
author | JulienMoumne <julien@piwik.org> | 2012-06-14 20:19:42 +0400 |
---|---|---|
committer | JulienMoumne <julien@piwik.org> | 2012-06-14 20:19:42 +0400 |
commit | f39a8e103f01ade174f601c3c5a68c4a874d3e6d (patch) | |
tree | 80757bdf57ceace5efb3c65571502f88aba18778 | |
parent | 17ec4cc66f81dd57724f1a58a2dcdaddba91af61 (diff) |
fixes #2708
refs #71
* PDFReports major refactoring. Any plugin can now add new kinds of reports. Required for #2708 and #3118.
* test report functionality ($idReport == 0) dropped in Piwik_PDFReports_API->generateReport()
* All Websites report shows 3 more metrics: Goal Conversions, eCommerce Conversions and eCommerce Revenue. Can be removed if asked to.
* Piwik_PDFReports_API->sendEmailReport renamed to sendReport
* All Piwik_PDFReports_API method signatures updated to support generic report parameters
refs #389
* new API method to retrieve only one Piwik site : Piwik_MultiSites_API->getOne()
* per #2708 description, Piwik_MultiSites_API methods now support a new parameter named enhanced. When activated, Goal Conversions, eCommerce Conversions and eCommerce Revenue along with their evolution will be included in the API output.
* API metrics refactored in (@ignored)Piwik_MultiSites_API->getApiMetrics()
* Metadata now returns 12 metrics : nb_visits, visits_evolution, nb_actions, actions_evolution, revenu, revenue_evolution, nb_conversions, nb_conversions_evolution, orders, orders_evolution, ecommerce_revenue, ecommerce_revenue_evolution
refs #3118
* ReportPublisher plugin could now easily be implemented
commits merged
* r6243
* r6422 (#3012)
TODO
* the MobileMessaging settings page may need some embellishment
* @review annotations need some attention
* test if the MultiSites API evolutions have some impact on Piwik Mobile and other client code
git-svn-id: http://dev.piwik.org/svn/trunk@6478 59fd770c-687e-43c8-a1e3-f5a4ff64c105
39 files changed, 3921 insertions, 623 deletions
diff --git a/core/Piwik.php b/core/Piwik.php index 6bd6810fb0..12a091d6a2 100644 --- a/core/Piwik.php +++ b/core/Piwik.php @@ -1687,6 +1687,17 @@ class Piwik } /** + * Returns Super User login + * + * @return string + */ + static public function getSuperUserLogin() + { + $superuser = Piwik_Config::getInstance()->superuser; + return $superuser['login']; + } + + /** * Returns Super User email * * @return string diff --git a/core/ReportRenderer.php b/core/ReportRenderer.php index 87300cf685..c68f1bb63b 100644 --- a/core/ReportRenderer.php +++ b/core/ReportRenderer.php @@ -27,9 +27,12 @@ abstract class Piwik_ReportRenderer const TABLE_CELL_BORDER_COLOR = "231,231,231"; const TABLE_BG_COLOR = "249,250,250"; - static public $availableReportRenderers = array( - 'pdf' => 'plugins/UserSettings/images/plugins/pdf.gif', - 'html' => 'themes/default/images/html_icon.png', + const HTML_FORMAT = 'html'; + const PDF_FORMAT = 'pdf'; + + static private $availableReportRenderers = array( + self::PDF_FORMAT, + self::HTML_FORMAT, ); protected $renderImageInline = false; @@ -56,7 +59,7 @@ abstract class Piwik_ReportRenderer throw new Exception( Piwik_TranslateException( 'General_ExceptionInvalidReportRendererFormat', - array($name, implode(', ', array_keys(self::$availableReportRenderers))) + array($name, implode(', ', self::$availableReportRenderers)) ) ); } @@ -143,6 +146,35 @@ abstract class Piwik_ReportRenderer return $outputFilename; } + protected static function writeFile($filename, $extension, $content) + { + $filename = self::appendExtension($filename, $extension); + $outputFilename = self::getOutputPath($filename); + + $emailReport = @fopen($outputFilename, "w"); + + if (!$emailReport) { + throw new Exception ("The file : " . $outputFilename . " can not be opened in write mode."); + } + + fwrite($emailReport, $content); + fclose($emailReport); + + return $outputFilename; + } + + protected static function sendToBrowser($filename, $extension, $contentType, $content) + { + $filename = Piwik_ReportRenderer::appendExtension($filename, $extension); + + Piwik::overrideCacheControlHeaders(); + header('Content-Description: File Transfer'); + header('Content-Type: ' . $contentType); + header('Content-Disposition: attachment; filename="'.str_replace('"', '\'', basename($filename)).'";'); + header('Content-Length: '.strlen($content)); + echo $content; + } + /** * Convert a dimension-less report to a multi-row two-column data table * diff --git a/core/ReportRenderer/Html.php b/core/ReportRenderer/Html.php index 925ee3b799..dc5c8320a8 100644 --- a/core/ReportRenderer/Html.php +++ b/core/ReportRenderer/Html.php @@ -36,33 +36,14 @@ class Piwik_ReportRenderer_Html extends Piwik_ReportRenderer { $this->epilogue(); - $filename = Piwik_ReportRenderer::appendExtension($filename, "html"); - $outputFilename = Piwik_ReportRenderer::getOutputPath($filename); - - $emailReport = @fopen($outputFilename, "w"); - - if (!$emailReport) { - throw new Exception ("The file : " . $outputFilename . " can not be opened in write mode."); - } - - fwrite($emailReport, $this->rendering); - fclose($emailReport); - - return $outputFilename; + return Piwik_ReportRenderer::writeFile($filename, 'html', $this->rendering); } public function sendToBrowserDownload($filename) { $this->epilogue(); - $filename = Piwik_ReportRenderer::appendExtension($filename, "html"); - - Piwik::overrideCacheControlHeaders(); - header('Content-Description: File Transfer'); - header('Content-Type: text/html'); - header('Content-Disposition: attachment; filename="'.str_replace('"', '\'', basename($filename)).'";'); - header('Content-Length: '.strlen($this->rendering)); - echo $this->rendering; + Piwik_ReportRenderer::sendToBrowser($filename, 'html', 'text/html', $this->rendering); } private function epilogue() diff --git a/core/Updates/1.8.3-b1.php b/core/Updates/1.8.3-b1.php new file mode 100644 index 0000000000..17f0a975f1 --- /dev/null +++ b/core/Updates/1.8.3-b1.php @@ -0,0 +1,99 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + * + * @category Piwik‚ + * @package Updates + */ + +/** + * @package Updates + */ +class Piwik_Updates_1_8_3_b1 extends Piwik_Updates +{ + + static function update() + { + try { + + // Piwik_Common::prefixTable('pdf') has been heavily refactored to be more generic + // The following actions are taken in this update script : + // - create the new generic report table Piwik_Common::prefixTable('report') + // - migrate previous reports, if any, from Piwik_Common::prefixTable('pdf') to Piwik_Common::prefixTable('report') + // - delete Piwik_Common::prefixTable('pdf') + Piwik_Query( + 'CREATE TABLE `'.Piwik_Common::prefixTable('report').'` ( + `idreport` INT(11) NOT NULL AUTO_INCREMENT, + `idsite` INTEGER(11) NOT NULL, + `login` VARCHAR(100) NOT NULL, + `description` VARCHAR(255) NOT NULL, + `period` VARCHAR(10) NOT NULL, + `type` VARCHAR(10) NOT NULL, + `format` VARCHAR(10) NOT NULL, + `reports` TEXT NOT NULL, + `parameters` TEXT NULL, + `ts_created` TIMESTAMP NULL, + `ts_last_sent` TIMESTAMP NULL, + `deleted` tinyint(4) NOT NULL default 0, + PRIMARY KEY (`idreport`) + ) DEFAULT CHARSET=utf8' + ); + + $reports = Piwik_FetchAll('SELECT * FROM `'. Piwik_Common::prefixTable('pdf') .'`'); + foreach($reports AS $report) { + + $idreport = $report['idreport']; + $idsite = $report['idsite']; + $login = $report['login']; + $description = $report['description']; + $period = $report['period']; + $format = $report['format']; + $display_format = $report['display_format']; + $email_me = $report['email_me']; + $additional_emails = $report['additional_emails']; + $reports = $report['reports']; + $ts_created = $report['ts_created']; + $ts_last_sent = $report['ts_last_sent']; + $deleted = $report['deleted']; + + $parameters = array(); + + if(!is_null($additional_emails)) + { + $parameters[Piwik_PDFReports::ADDITIONAL_EMAILS_PARAMETER] = preg_split('/,/', $additional_emails); + } + + $parameters[Piwik_PDFReports::EMAIL_ME_PARAMETER] = is_null($email_me) ? Piwik_PDFReports::EMAIL_ME_PARAMETER_DEFAULT_VALUE : filter_var($email_me, FILTER_VALIDATE_BOOLEAN); + $parameters[Piwik_PDFReports::DISPLAY_FORMAT_PARAMETER] = $display_format; + + Piwik_Query( + 'INSERT INTO `' . Piwik_Common::prefixTable('report') . '` SET + idreport = ?, idsite = ?, login = ?, description = ?, period = ?, + type = ?, format = ?, reports = ?, parameters = ?, ts_created = ?, + ts_last_sent = ?, deleted = ?', + array( + $idreport, + $idsite, + $login, + $description, + is_null($period) ? Piwik_PDFReports::DEFAULT_PERIOD : $period, + Piwik_PDFReports::EMAIL_TYPE, + is_null($format) ? Piwik_PDFReports::DEFAULT_REPORT_FORMAT : $format, + json_encode(preg_split('/,/', $reports)), + json_encode($parameters), + $ts_created, + $ts_last_sent, + $deleted + ) + ); + } + + Piwik_Query('DROP TABLE `'. Piwik_Common::prefixTable('pdf') .'`'); + } + catch(Exception $e){} + } +} diff --git a/core/Version.php b/core/Version.php index f82fc394c6..009b5cebaa 100644 --- a/core/Version.php +++ b/core/Version.php @@ -21,5 +21,5 @@ final class Piwik_Version * Current Piwik version * @var string */ - const VERSION = '1.8.2'; + const VERSION = '1.8.3-b1'; } diff --git a/lang/en.php b/lang/en.php index 9a6c87f7a1..874e400322 100644 --- a/lang/en.php +++ b/lang/en.php @@ -12,6 +12,7 @@ $translations = array( 'General_DashboardForASpecificWebsite' => 'Dashboard for a specific website', 'General_MultiSitesSummary' => 'All Websites', 'General_AllWebsitesDashboard' => 'All Websites dashboard', + 'General_SingleWebsitesDashboard' => 'Single Websites dashboard', 'General_API' => 'API', 'General_Widgets' => 'Widgets', 'General_Settings' => 'Settings', @@ -1587,6 +1588,8 @@ Note: this token will expire in 24 hrs.', 'PDFReports_SendReportNow' => 'Send Report now', 'PDFReports_EmailSchedule' => 'Email Schedule', 'PDFReports_SendReportTo' => 'Send report to', + 'PDFReports_NoRecipients' => 'This report has no recipients', + 'PDFReports_ReportType' => 'Send report via', 'PDFReports_ReportFormat' => 'Report Format', 'PDFReports_AggregateReportsFormat' => '(optional) Display options', 'PDFReports_AggregateReportsFormat_TablesOnly' => '(default) Display Report tables (Graphs only for key metrics)', diff --git a/plugins/MobileMessaging/API.php b/plugins/MobileMessaging/API.php new file mode 100644 index 0000000000..5de08d0a94 --- /dev/null +++ b/plugins/MobileMessaging/API.php @@ -0,0 +1,448 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + * + * @category Piwik_Plugins + * @package Piwik_MobileMessaging + */ + +/** + * The MobileMessaging API lets you manage and access all the MobileMessaging plugin features including : + * - manage SMS API credential + * - activate phone numbers + * - check remaining credits + * - send SMS + * @package Piwik_MobileMessaging + */ +class Piwik_MobileMessaging_API +{ + const VERIFICATION_CODE_LENGTH = 5; + const SMS_FROM = 'Piwik'; + + static private $instance = null; + + /** + * @return Piwik_MobileMessaging_API + */ + static public function getInstance() + { + if (self::$instance == null) + { + self::$instance = new self; + } + return self::$instance; + } + + /** + * @return Piwik_MobileMessaging_SMSProvider + */ + static private function getSMSProviderInstance($provider) + { + return Piwik_MobileMessaging_SMSProvider::factory($provider); + } + + /** + * determine if SMS API credential are available for the current user + * + * @return bool true if SMS API credential are available for the current user + */ + public function areSMSAPICredentialProvided() + { + Piwik::checkUserIsNotAnonymous(); + + $credential = $this->getSMSAPICredential(); + return + isset($credential[Piwik_MobileMessaging::USERNAME_OPTION]) + && !empty($credential[Piwik_MobileMessaging::USERNAME_OPTION]); + } + + private function getSMSAPICredential() + { + $settings = $this->getCredentialManagerSettings(); + return array( + Piwik_MobileMessaging::PROVIDER_OPTION => + isset($settings[Piwik_MobileMessaging::PROVIDER_OPTION]) ? $settings[Piwik_MobileMessaging::PROVIDER_OPTION] : null, + Piwik_MobileMessaging::USERNAME_OPTION => + isset($settings[Piwik_MobileMessaging::USERNAME_OPTION]) ? $settings[Piwik_MobileMessaging::USERNAME_OPTION] : null, + Piwik_MobileMessaging::PASSWORD_OPTION => + isset($settings[Piwik_MobileMessaging::PASSWORD_OPTION]) ? $settings[Piwik_MobileMessaging::PASSWORD_OPTION] : null, + ); + } + + /** + * return the username of the SMS API account for the current user + * + * @return string username of the SMS API account + */ + public function getAPIUsername() + { + $this->checkCredentialManagementRights(); + $credential = $this->getSMSAPICredential(); + return $credential[Piwik_MobileMessaging::USERNAME_OPTION]; + } + + /** + * return the SMS API Provider for the current user + * + * @return string SMS API Provider + */ + public function getSMSProvider() + { + $this->checkCredentialManagementRights(); + $credential = $this->getSMSAPICredential(); + return $credential[Piwik_MobileMessaging::PROVIDER_OPTION]; + } + + /** + * set the SMS API credential + * + * @param string $provider SMS API provider + * @param string $username SMS API username + * @param string $password SMS API password + * + * @return bool true if SMS API credential were validated and saved, false otherwise + */ + public function setSMSAPICredential($provider, $username, $password) + { + $this->checkCredentialManagementRights(); + + $smsProviderInstance = self::getSMSProviderInstance($provider); + $smsProviderInstance->verifyCredential($username, $password); + + $settings = $this->getCredentialManagerSettings(); + + $settings[Piwik_MobileMessaging::PROVIDER_OPTION] = $provider; + $settings[Piwik_MobileMessaging::USERNAME_OPTION] = $username; + $settings[Piwik_MobileMessaging::PASSWORD_OPTION] = $password; + + $this->setCredentialManagerSettings($settings); + + return true; + } + + /** + * add phone number + * + * @param string $phoneNumber + * + * @return bool true + */ + public function addPhoneNumber($phoneNumber) + { + Piwik::checkUserIsNotAnonymous(); + + $verificationCode = ""; + for($i = 0; $i < self::VERIFICATION_CODE_LENGTH; $i++) + { + $verificationCode .= rand(0,9); + } + + $smsText = Piwik_Translate('MobileMessaging_VerificationText', $verificationCode); + + $this->sendSMS($smsText, $phoneNumber, self::SMS_FROM); + + $phoneNumbers = $this->retrievePhoneNumbers(); + $phoneNumbers[$phoneNumber] = $verificationCode; + $this->savePhoneNumbers($phoneNumbers); + + return true; + } + + /** + * send a SMS + * + * @param string $phoneNumber + * + * @return bool true + */ + public function sendSMS($content, $phoneNumber, $from) + { + Piwik::checkUserIsNotAnonymous(); + + $credential = $this->getSMSAPICredential(); + $SMSProvider = self::getSMSProviderInstance($credential[Piwik_MobileMessaging::PROVIDER_OPTION]); + $SMSProvider->sendSMS( + $credential[Piwik_MobileMessaging::USERNAME_OPTION], + $credential[Piwik_MobileMessaging::PASSWORD_OPTION], + $content, + $phoneNumber, + $from + ); + + return true; + } + + /** + * get remaining credit + * + * @return string remaining credit + */ + public function getCreditLeft() + { + $this->checkCredentialManagementRights(); + + $credential = $this->getSMSAPICredential(); + $SMSProvider = self::getSMSProviderInstance($credential[Piwik_MobileMessaging::PROVIDER_OPTION]); + return $SMSProvider->getCreditLeft( + $credential[Piwik_MobileMessaging::USERNAME_OPTION], + $credential[Piwik_MobileMessaging::PASSWORD_OPTION] + ); + } + + /** + * remove phone number + * + * @param string $phoneNumber + * + * @return bool true + */ + public function removePhoneNumber($phoneNumber) + { + Piwik::checkUserIsNotAnonymous(); + + $phoneNumbers = $this->retrievePhoneNumbers(); + unset($phoneNumbers[$phoneNumber]); + $this->savePhoneNumbers($phoneNumbers); + + // remove phone number from reports + $pdfReportsAPIInstance = Piwik_PDFReports_API::getInstance(); + $reports = $pdfReportsAPIInstance->getReports( + $idSite = false, + $period = false, + $idReport = false, + $ifSuperUserReturnOnlySuperUserReports = $this->getDelegatedManagement() + ); + + foreach($reports as $report) + { + if ($report['type'] == Piwik_MobileMessaging::MOBILE_TYPE) + { + $reportParameters = $report['parameters']; + $reportPhoneNumbers = $reportParameters[Piwik_MobileMessaging::PHONE_NUMBERS_PARAMETER]; + $updatedPhoneNumbers = array(); + foreach($reportPhoneNumbers as $reportPhoneNumber) + { + if($reportPhoneNumber != $phoneNumber) + { + $updatedPhoneNumbers[] = $reportPhoneNumber; + } + } + + if(count($updatedPhoneNumbers) != count($reportPhoneNumbers)) + { + $reportParameters[Piwik_MobileMessaging::PHONE_NUMBERS_PARAMETER] = $updatedPhoneNumbers; + + $pdfReportsAPIInstance->updateReport( + $report['idreport'], + $report['idsite'], + $report['description'], + $report['period'], + $report['type'], + $report['format'], + $report['reports'], + $reportParameters + ); + } + } + } + + return true; + } + + private function retrievePhoneNumbers() + { + $settings = $this->getCurrentUserSettings(); + + $phoneNumbers = array(); + if(isset($settings[Piwik_MobileMessaging::PHONE_NUMBERS_OPTION])) + { + $phoneNumbers = $settings[Piwik_MobileMessaging::PHONE_NUMBERS_OPTION]; + } + + return $phoneNumbers; + } + + private function savePhoneNumbers($phoneNumbers) + { + $settings = $this->getCurrentUserSettings(); + + $settings[Piwik_MobileMessaging::PHONE_NUMBERS_OPTION] = $phoneNumbers; + + $this->setCurrentUserSettings($settings); + } + + /** + * validate phone number + * + * @param string $phoneNumber + * @param string $verificationCode + * + * @return bool true if validation code is correct, false otherwise + */ + public function validatePhoneNumber($phoneNumber, $verificationCode) + { + Piwik::checkUserIsNotAnonymous(); + + $phoneNumbers = $this->retrievePhoneNumbers(); + + if(isset($phoneNumbers[$phoneNumber])) + { + if($verificationCode == $phoneNumbers[$phoneNumber]) { + + $phoneNumbers[$phoneNumber] = null; + $this->savePhoneNumbers($phoneNumbers); + return true; + } + } + + return false; + } + + /** + * get phone number list + * + * @return array $phoneNumber => $isValidated + */ + public function getPhoneNumbers() + { + Piwik::checkUserIsNotAnonymous(); + + $rawPhoneNumbers = $this->retrievePhoneNumbers(); + + $phoneNumbers = array(); + foreach($rawPhoneNumbers as $phoneNumber => $verificationCode) + { + $phoneNumbers[$phoneNumber] = self::isActivated($verificationCode); + } + + return $phoneNumbers; + } + + /** + * get activated phone number list + * + * @return array $phoneNumber + */ + public function getActivatedPhoneNumbers() + { + Piwik::checkUserIsNotAnonymous(); + + $phoneNumbers = $this->retrievePhoneNumbers(); + + $activatedPhoneNumbers = array(); + foreach($phoneNumbers as $phoneNumber => $verificationCode) + { + if(self::isActivated($verificationCode)) + { + $activatedPhoneNumbers[] = $phoneNumber; + } + } + + return $activatedPhoneNumbers; + } + + private static function isActivated($verificationCode) + { + return $verificationCode === null; + } + + /** + * delete the SMS API credential + * + * @return bool true + */ + public function deleteSMSAPICredential() + { + $this->checkCredentialManagementRights(); + + $settings = $this->getCredentialManagerSettings(); + + $settings[Piwik_MobileMessaging::USERNAME_OPTION] = null; + $settings[Piwik_MobileMessaging::PASSWORD_OPTION] = null; + + $this->setCredentialManagerSettings($settings); + + return true; + } + + private function checkCredentialManagementRights() + { + $this->getDelegatedManagement() ? Piwik::checkUserIsNotAnonymous() : Piwik::checkUserIsSuperUser(); + } + + private function setUserSettings($user, $settings) + { + Piwik_SetOption( + $user . Piwik_MobileMessaging::USER_SETTINGS_POSTFIX_OPTION, + json_encode($settings) + ); + } + + private function setCurrentUserSettings($settings) + { + $this->setUserSettings(Piwik::getCurrentUserLogin(), $settings); + } + + private function setCredentialManagerSettings($settings) + { + $this->setUserSettings($this->getCredentialManagerLogin(), $settings); + } + + private function getCredentialManagerLogin() + { + return $this->getDelegatedManagement() ? Piwik::getCurrentUserLogin() : Piwik::getSuperUserLogin(); + } + + private function getUserSettings($user) + { + $optionIndex = $user . Piwik_MobileMessaging::USER_SETTINGS_POSTFIX_OPTION; + $userSettings = Piwik_GetOption($optionIndex); + + if(empty($userSettings)) + { + $userSettings = array(); + } + else + { + $userSettings = json_decode($userSettings, true); + } + + return $userSettings; + } + + private function getCredentialManagerSettings() + { + return $this->getUserSettings($this->getCredentialManagerLogin()); + } + + private function getCurrentUserSettings() + { + return $this->getUserSettings(Piwik::getCurrentUserLogin()); + } + + /** + * Specify if normal users can manage their own SMS API credential + * + * @param bool $delegatedManagement false if SMS API credential only manageable by super admin, true otherwise + */ + public function setDelegatedManagement($delegatedManagement) + { + Piwik::checkUserIsSuperUser(); + Piwik_SetOption(Piwik_MobileMessaging::DELEGATED_MANAGEMENT_OPTION, $delegatedManagement); + } + + /** + * Determine if normal users can manage their own SMS API credential + * + * @return bool false if SMS API credential only manageable by super admin, true otherwise + */ + public function getDelegatedManagement() + { + Piwik::checkUserIsNotAnonymous(); + return Piwik_GetOption(Piwik_MobileMessaging::DELEGATED_MANAGEMENT_OPTION) == 'true'; + } +} diff --git a/plugins/MobileMessaging/APIException.php b/plugins/MobileMessaging/APIException.php new file mode 100644 index 0000000000..c8846dcb35 --- /dev/null +++ b/plugins/MobileMessaging/APIException.php @@ -0,0 +1,19 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + * + * @category Piwik_Plugins + * @package Piwik_MobileMessaging + */ + +/** + * @package Piwik_MobileMessaging + */ +class Piwik_MobileMessaging_APIException extends Exception +{ + +} diff --git a/plugins/MobileMessaging/Controller.php b/plugins/MobileMessaging/Controller.php new file mode 100644 index 0000000000..619c39b7a0 --- /dev/null +++ b/plugins/MobileMessaging/Controller.php @@ -0,0 +1,78 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + * + * @category Piwik_Plugins + * @package Piwik_MobileMessaging + */ + +require_once PIWIK_INCLUDE_PATH . '/plugins/UserCountry/functions.php'; + +/** + * + * @package Piwik_MobileMessaging + */ +class Piwik_MobileMessaging_Controller extends Piwik_Controller_Admin +{ + /* + * Mobile Messaging Settings tab : + * - set delegated management + * - provide & validate SMS API credential + * - add & activate phone numbers + * - check remaining credits + */ + function index() + { + Piwik::checkUserIsNotAnonymous(); + + $view = Piwik_View::factory('Settings'); + + $view->isSuperUser = Piwik::isUserIsSuperUser(); + + $mobileMessagingAPI = Piwik_MobileMessaging_API::getInstance(); + $view->delegatedManagement = $mobileMessagingAPI->getDelegatedManagement(); + $view->credentialSupplied = $mobileMessagingAPI->areSMSAPICredentialProvided(); + $view->accountManagedByCurrentUser = $view->isSuperUser || $view->delegatedManagement; + + if($view->credentialSupplied && $view->accountManagedByCurrentUser) + { + $view->provider = $mobileMessagingAPI->getSMSProvider(); + $view->APIUsername = $mobileMessagingAPI->getAPIUsername(); + $view->creditLeft = $mobileMessagingAPI->getCreditLeft(); + } + + $view->smsProviders = Piwik_MobileMessaging_SMSProvider::$availableSMSProviders; + + // construct the list of countries from the lang files + $countries = array(); + foreach(Piwik_Common::getCountriesList() as $countryCode => $continentCode) + { + if(isset(Piwik_MobileMessaging_CountryCallingCodes::$countryCallingCodes[$countryCode])) + { + $countries[$countryCode] = + array( + 'countryName' => Piwik_CountryTranslate($countryCode), + 'countryCallingCode' => Piwik_MobileMessaging_CountryCallingCodes::$countryCallingCodes[$countryCode], + ); + } + } + $view->countries = $countries; + + $view->defaultCountry = Piwik_Common::getCountry( + Piwik_LanguagesManager::getLanguageCodeForCurrentUser(), + true, + Piwik_IP::getIpFromHeader() + ); + + $view->phoneNumbers = $mobileMessagingAPI->getPhoneNumbers(); + + $this->setBasicVariablesView($view); + + $view->menu = Piwik_GetAdminMenu(); + echo $view->render(); + } +} diff --git a/plugins/MobileMessaging/CountryCallingCodes.php b/plugins/MobileMessaging/CountryCallingCodes.php new file mode 100644 index 0000000000..7313b5937e --- /dev/null +++ b/plugins/MobileMessaging/CountryCallingCodes.php @@ -0,0 +1,273 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + * + * @category Piwik_Plugins + * @package Piwik_MobileMessaging + */ + +/** + * + * @package Piwik_MobileMessaging + */ +class Piwik_MobileMessaging_CountryCallingCodes +{ + // @review is this the correct way to store calling codes or should we use GLOBALS like in core/DataFiles/Countries.php? + // list taken from core/DataFiles/Countries.php + public static $countryCallingCodes = array( + 'ad' => '376', + 'ae' => '971', + 'af' => '93', + 'ag' => '1268', // @wikipedia original value: 1 268 + 'ai' => '1264', // @wikipedia original value: 1 264 + 'al' => '355', + 'am' => '374', + 'ao' => '244', +// 'aq' => 'MISSING CODE', // @wikipedia In Antarctica dialing is dependent on the parent country of each base + 'ar' => '54', + 'as' => '1684', // @wikipedia original value: 1 684 + 'at' => '43', + 'au' => '61', + 'aw' => '297', + 'ax' => '358', + 'az' => '994', + 'ba' => '387', + 'bb' => '1246', // @wikipedia original value: 1 246 + 'bd' => '880', + 'be' => '32', + 'bf' => '226', + 'bg' => '359', + 'bh' => '973', + 'bi' => '257', + 'bj' => '229', + 'bl' => '590', + 'bm' => '1441', // @wikipedia original value: 1 441 + 'bn' => '673', + 'bo' => '591', + 'bq' => '5997', // @wikipedia original value: 599 7 + 'br' => '55', + 'bs' => '1242', // @wikipedia original value: 1 242 + 'bt' => '975', +// 'bv' => 'MISSING CODE', + 'bw' => '267', + 'by' => '375', + 'bz' => '501', + 'ca' => '1', + 'cc' => '61', + 'cd' => '243', + 'cf' => '236', + 'cg' => '242', + 'ch' => '41', + 'ci' => '225', + 'ck' => '682', + 'cl' => '56', + 'cm' => '237', + 'cn' => '86', + 'co' => '57', + 'cr' => '506', + 'cu' => '53', + 'cv' => '238', + 'cw' => '5999', // @wikipedia original value: 599 9 + 'cx' => '61', + 'cy' => '357', + 'cz' => '420', + 'de' => '49', + 'dj' => '253', + 'dk' => '45', + 'dm' => '1767', // @wikipedia original value: 1 767 +// 'do' => 'MISSING CODE', // @wikipedia original values: 1 809, 1 829, 1 849 + 'dz' => '213', + 'ec' => '593', + 'ee' => '372', + 'eg' => '20', + 'eh' => '212', + 'er' => '291', + 'es' => '34', + 'et' => '251', + 'fi' => '358', + 'fj' => '679', + 'fk' => '500', + 'fm' => '691', + 'fo' => '298', + 'fr' => '33', + 'ga' => '241', + 'gb' => '44', + 'gd' => '1473', // @wikipedia original value: 1 473 + 'ge' => '995', + 'gf' => '594', + 'gg' => '44', + 'gh' => '233', + 'gi' => '350', + 'gl' => '299', + 'gm' => '220', + 'gn' => '224', + 'gp' => '590', + 'gq' => '240', + 'gr' => '30', + 'gs' => '500', + 'gt' => '502', + 'gu' => '1671', // @wikipedia original value: 1 671 + 'gw' => '245', + 'gy' => '592', + 'hk' => '852', +// 'hm' => 'MISSING CODE', + 'hn' => '504', + 'hr' => '385', + 'ht' => '509', + 'hu' => '36', + 'id' => '62', + 'ie' => '353', + 'il' => '972', + 'im' => '44', + 'in' => '91', + 'io' => '246', + 'iq' => '964', + 'ir' => '98', + 'is' => '354', + 'it' => '39', + 'je' => '44', + 'jm' => '1876', // @wikipedia original value: 1 876 + 'jo' => '962', + 'jp' => '81', + 'ke' => '254', + 'kg' => '996', + 'kh' => '855', + 'ki' => '686', + 'km' => '269', + 'kn' => '1869', // @wikipedia original value: 1 869 + 'kp' => '850', + 'kr' => '82', + 'kw' => '965', + 'ky' => '1345', // @wikipedia original value: 1 345 +// 'kz' => 'MISSING CODE', // @wikipedia original values: 7 6, 7 7 + 'la' => '856', + 'lb' => '961', + 'lc' => '1758', // @wikipedia original value: 1 758 + 'li' => '423', + 'lk' => '94', + 'lr' => '231', + 'ls' => '266', + 'lt' => '370', + 'lu' => '352', + 'lv' => '371', + 'ly' => '218', + 'ma' => '212', + 'mc' => '377', + 'md' => '373', + 'me' => '382', + 'mf' => '590', + 'mg' => '261', + 'mh' => '692', + 'mk' => '389', + 'ml' => '223', + 'mm' => '95', + 'mn' => '976', + 'mo' => '853', + 'mp' => '1670', // @wikipedia original value: 1 670 + 'mq' => '596', + 'mr' => '222', + 'ms' => '1664', // @wikipedia original value: 1 664 + 'mt' => '356', + 'mu' => '230', + 'mv' => '960', + 'mw' => '265', + 'mx' => '52', + 'my' => '60', + 'mz' => '258', + 'na' => '264', + 'nc' => '687', + 'ne' => '227', + 'nf' => '672', + 'ng' => '234', + 'ni' => '505', + 'nl' => '31', + 'no' => '47', + 'np' => '977', + 'nr' => '674', + 'nu' => '683', + 'nz' => '64', + 'om' => '968', + 'pa' => '507', + 'pe' => '51', + 'pf' => '689', + 'pg' => '675', + 'ph' => '63', + 'pk' => '92', + 'pl' => '48', + 'pm' => '508', + 'pn' => '672', +// 'pr' => 'MISSING CODE', // @wikipedia original values: 1 787, 1 939 + 'ps' => '970', + 'pt' => '351', + 'pw' => '680', + 'py' => '595', + 'qa' => '974', + 're' => '262', + 'ro' => '40', + 'rs' => '381', + 'ru' => '7', + 'rw' => '250', + 'sa' => '966', + 'sb' => '677', + 'sc' => '248', + 'sd' => '249', + 'se' => '46', + 'sg' => '65', + 'sh' => '290', + 'si' => '386', + 'sj' => '47', + 'sk' => '421', + 'sl' => '232', + 'sm' => '378', + 'sn' => '221', + 'so' => '252', + 'sr' => '597', + 'ss' => '211', + 'st' => '239', + 'sv' => '503', + 'sx' => '1721', //@wikipedia original value: 1 721 + 'sy' => '963', + 'sz' => '268', + 'tc' => '1649', // @wikipedia original value: 1 649 + 'td' => '235', +// 'tf' => 'MISSING CODE', + 'tg' => '228', + 'th' => '66', +// 'ti' => 'MISSING CODE', + 'tj' => '992', + 'tk' => '690', + 'tl' => '670', + 'tm' => '993', + 'tn' => '216', + 'to' => '676', + 'tr' => '90', + 'tt' => '1868', // @wikipedia original value: 1 868 + 'tv' => '688', + 'tw' => '886', + 'tz' => '255', + 'ua' => '380', + 'ug' => '256', +// 'um' => 'MISSING CODE', + 'us' => '1', + 'uy' => '598', + 'uz' => '998', +// 'va' => 'MISSING CODE', // @wikipedia original values: 39 066, assigned 379 + 'vc' => '1784', // @wikipedia original value: 1 784 + 've' => '58', + 'vg' => '1284', // @wikipedia original value: 1 284 + 'vi' => '1340', // @wikipedia original value: 1 340 + 'vn' => '84', + 'vu' => '678', + 'wf' => '681', + 'ws' => '685', + 'ye' => '967', + 'yt' => '262', + 'za' => '27', + 'zm' => '260', + 'zw' => '263' + ); +} diff --git a/plugins/MobileMessaging/MobileMessaging.php b/plugins/MobileMessaging/MobileMessaging.php new file mode 100644 index 0000000000..e52c2cfdc4 --- /dev/null +++ b/plugins/MobileMessaging/MobileMessaging.php @@ -0,0 +1,309 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + * + * @category Piwik_Plugins + * @package Piwik_MobileMessaging + */ + +/** + * + * @package Piwik_MobileMessaging + */ +class Piwik_MobileMessaging extends Piwik_Plugin +{ + const DELEGATED_MANAGEMENT_OPTION = 'MobileMessaging_DelegatedManagement'; + const PROVIDER_OPTION = 'Provider'; + const USERNAME_OPTION = 'Username'; + const PASSWORD_OPTION = 'Password'; + const PHONE_NUMBERS_OPTION = 'PhoneNumbers'; + const DELEGATED_MANAGEMENT_OPTION_DEFAULT = 'false'; + const USER_SETTINGS_POSTFIX_OPTION = '_MobileMessagingSettings'; + + const PHONE_NUMBERS_PARAMETER = 'phoneNumbers'; + + const MOBILE_TYPE = 'mobile'; + const SMS_FORMAT = 'sms'; + + static private $availableParameters = array( + self::PHONE_NUMBERS_PARAMETER => true, + ); + + static private $managedReportTypes = array( + self::MOBILE_TYPE => 'plugins/MobileMessaging/images/phone.png' + ); + + static private $managedReportFormats = array( + self::SMS_FORMAT => 'plugins/MobileMessaging/images/phone.png' + ); + + static private $availableReports = array( + array( + 'module' => 'MultiSites', + 'action' => 'getAll', + ), + array( + 'module' => 'MultiSites', + 'action' => 'getOne', + ), + ); + + /** + * Return information about this plugin. + * + * @see Piwik_Plugin + * + * @return array + */ + public function getInformation() + { + return array( + 'name' => 'Mobile Messaging Plugin', + 'description' => Piwik_Translate('MobileMessaging_PluginDescription'), + 'homepage' => 'http://piwik.org/', + 'author' => 'Piwik', + 'author_homepage' => 'http://piwik.org/', + 'license' => 'GPL v3 or later', + 'license_homepage' => 'http://www.gnu.org/licenses/gpl.html', + 'version' => Piwik_Version::VERSION, + 'translationAvailable' => true, + ); + } + + function getListHooksRegistered() + { + return array( + 'AdminMenu.add' => 'addMenu', + 'AssetManager.getJsFiles' => 'getJsFiles', + 'PDFReports.getReportParameters' => 'getReportParameters', + 'PDFReports.validateReportParameters' => 'validateReportParameters', + 'PDFReports.getReportMetadata' => 'getReportMetadata', + 'PDFReports.getReportTypes' => 'getReportTypes', + 'PDFReports.getReportFormats' => 'getReportFormats', + 'PDFReports.getRendererInstance' => 'getRendererInstance', + 'PDFReports.getReportRecipients' => 'getReportRecipients', + 'PDFReports.allowMultipleReports' => 'allowMultipleReports', + 'PDFReports.sendReport' => 'sendReport', + 'template_reportParametersPDFReports' => 'template_reportParametersPDFReports', + ); + } + + function addMenu() + { + Piwik_AddAdminMenu( + 'MobileMessaging_SettingsMenu', + array('module' => 'MobileMessaging', 'action' => 'index'), + true + ); + } + + /** + * Get JavaScript files + * + * @param Piwik_Event_Notification $notification notification object + */ + function getJsFiles( $notification ) + { + $jsFiles = &$notification->getNotificationObject(); + + $jsFiles[] = "plugins/MobileMessaging/scripts/MobileMessagingSettings.js"; + $jsFiles[] = "plugins/MobileMessaging/scripts/jquery.select-to-autocomplete.js"; // @review should this go in the LEGALNOTICE file ? + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function validateReportParameters( $notification ) + { + if(self::manageEvent($notification)) + { + $parameters = &$notification->getNotificationObject(); + + // phone number validation + $availablePhoneNumbers = Piwik_MobileMessaging_API::getInstance()->getActivatedPhoneNumbers(); + + $phoneNumbers = $parameters[self::PHONE_NUMBERS_PARAMETER]; + foreach($phoneNumbers as $key => $phoneNumber) + { + //@review when a wrong phone number is supplied we silently discard it, should an exception be raised? + if(!in_array($phoneNumber, $availablePhoneNumbers)) + { + unset($phoneNumbers[$key]); + } + } + + // 'unset' seems to transform the array to an associative array + $parameters[self::PHONE_NUMBERS_PARAMETER] = array_values($phoneNumbers); + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportMetadata( $notification ) + { + if(self::manageEvent($notification)) + { + $availableReportMetadata = &$notification->getNotificationObject(); + + $notificationInfo = $notification->getNotificationInfo(); + $idSite = $notificationInfo[Piwik_PDFReports_API::ID_SITE_INFO_KEY]; + + foreach(self::$availableReports as $availableReport) + { + $reportMetadata = Piwik_API_API::getInstance()->getMetadata( + $idSite, + $availableReport['module'], + $availableReport['action'] + ); + $reportMetadata = reset($reportMetadata); + + $availableReportMetadata[] = $reportMetadata; + } + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportTypes( $notification ) + { + $reportTypes = &$notification->getNotificationObject(); + $reportTypes = array_merge($reportTypes, self::$managedReportTypes); + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportFormats( $notification ) + { + if(self::manageEvent($notification)) + { + $reportFormats = &$notification->getNotificationObject(); + $reportFormats = array_merge($reportFormats, self::$managedReportFormats); + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportParameters( $notification ) + { + if(self::manageEvent($notification)) + { + $availableParameters = &$notification->getNotificationObject(); + $availableParameters = self::$availableParameters; + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getRendererInstance( $notification ) + { + if(self::manageEvent($notification)) + { + $reportRenderer = &$notification->getNotificationObject(); + $reportRenderer = new Piwik_MobileMessaging_ReportRenderer_Sms(); + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function allowMultipleReports( $notification ) + { + if(self::manageEvent($notification)) + { + $allowMultipleReports = &$notification->getNotificationObject(); + $allowMultipleReports = false; + } + } + + function getReportRecipients( $notification ) + { + if(self::manageEvent($notification)) + { + $recipients = &$notification->getNotificationObject(); + $notificationInfo = $notification->getNotificationInfo(); + + $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]; + $recipients = $report['parameters'][self::PHONE_NUMBERS_PARAMETER]; + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function sendReport( $notification ) + { + if(self::manageEvent($notification)) + { + $notificationInfo = $notification->getNotificationInfo(); + $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]; + $websiteName = $notificationInfo[Piwik_PDFReports_API::WEBSITE_NAME_KEY]; + $contents = $notificationInfo[Piwik_PDFReports_API::REPORT_CONTENT_KEY]; + + $parameters = $report['parameters']; + $phoneNumbers = $parameters[self::PHONE_NUMBERS_PARAMETER]; + + $mobileMessagingAPI = Piwik_MobileMessaging_API::getInstance(); + foreach($phoneNumbers as $phoneNumber) + { + $mobileMessagingAPI->sendSMS( + $contents, + $phoneNumber, + $websiteName + ); + } + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + static public function template_reportParametersPDFReports($notification) + { + $out =& $notification->getNotificationObject(); + + $view = Piwik_View::factory('ReportParameters'); + $view->reportType = self::MOBILE_TYPE; + $view->phoneNumbers = Piwik_MobileMessaging_API::getInstance()->getActivatedPhoneNumbers(); + $out .= $view->render(); + } + + private static function manageEvent($notification) + { + $notificationInfo = $notification->getNotificationInfo(); + return in_array($notificationInfo[Piwik_PDFReports_API::REPORT_TYPE_INFO_KEY], array_keys(self::$managedReportTypes)); + } + + function install() + { + $delegatedManagement = Piwik_GetOption(self::DELEGATED_MANAGEMENT_OPTION); + if (empty($delegatedManagement)) + { + Piwik_SetOption(self::DELEGATED_MANAGEMENT_OPTION, self::DELEGATED_MANAGEMENT_OPTION_DEFAULT); + } + } + + //@review should we also delete the plugin settings (API credentials, phone numbers) located in table piwik_option? + function deactivate() + { + // delete all mobile reports + $pdfReportsAPIInstance = Piwik_PDFReports_API::getInstance(); + $reports = $pdfReportsAPIInstance->getReports(); + + foreach($reports as $report) + { + if ($report['type'] == Piwik_MobileMessaging::MOBILE_TYPE) + { + $pdfReportsAPIInstance->deleteReport($report['idreport']); + } + } + } +} diff --git a/plugins/MobileMessaging/ReportRenderer/Sms.php b/plugins/MobileMessaging/ReportRenderer/Sms.php new file mode 100644 index 0000000000..5f3662fc59 --- /dev/null +++ b/plugins/MobileMessaging/ReportRenderer/Sms.php @@ -0,0 +1,115 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + * + * @category Piwik_Plugins + * @package Piwik_MobileMessaging_ReportRenderer + */ + +const FLOAT_REGEXP = '/[-+]?[0-9]*[\.,]?[0-9]+/'; + +/** + * + * @package Piwik_MobileMessaging_ReportRenderer + */ +class Piwik_MobileMessaging_ReportRenderer_Sms extends Piwik_ReportRenderer +{ + private $rendering = ""; + + public function setLocale($locale) + { + // nothing to do + } + + public function sendToDisk($filename) + { + return Piwik_ReportRenderer::writeFile($filename, 'sms', $this->rendering); + } + + public function sendToBrowserDownload($filename) + { + Piwik_ReportRenderer::sendToBrowser($filename, 'sms', 'text/plain', $this->rendering); + } + + public function renderFrontPage($websiteName, $prettyDate, $description, $reportMetadata) + { + // nothing to do + } + + public function renderReport($processedReport) + { + $isGoalPluginEnabled = Piwik_Common::isGoalPluginEnabled(); + $prettyDate = $processedReport['prettyDate']; + $reportData = $processedReport['reportData']; + + $evolutionMetrics = array(); + $multiSitesAPIMetrics = Piwik_MultiSites_API::getApiMetrics($enhanced = true); + foreach($multiSitesAPIMetrics as $metricSettings) + { + $evolutionMetrics[] = $metricSettings[Piwik_MultiSites_API::METRIC_EVOLUTION_COL_NAME_KEY]; + } + + // no decimal for all metrics to shorten SMS content (keeps the monetary sign for revenue metrics) + $reportData->filter( + 'ColumnCallbackReplace', + array( + array_merge(array_keys($multiSitesAPIMetrics),$evolutionMetrics), + create_function ( + '$value', + ' + return preg_replace_callback ( + FLOAT_REGEXP, + create_function ( + \'$matches\', + \'return round($matches[0]);\' + ), + $value + ); + ' + ) + ) + ); + + // evolution metrics formatting : + // - remove monetary, percentage and white spaces to shorten SMS content + // (this is also needed to be able to test $value != 0 and see if there is an evolution at all in SMSReport.tpl) + // - adds a plus sign + $reportData->filter( + 'ColumnCallbackReplace', + array( + $evolutionMetrics, + create_function ( + '$value', + ' + $matched = preg_match(FLOAT_REGEXP, $value, $matches); + return $matched ? sprintf("%+d",$matches[0]) : $value; + ' + ) + ) + ); + + $dataRows = $reportData->getRows(); + $reportMetadata = $processedReport['reportMetadata']; + $reportRowsMetadata = $reportMetadata->getRows(); + + $siteHasECommerce = array(); + foreach($reportRowsMetadata as $rowMetadata) + { + $idSite = $rowMetadata->getColumn('idsite'); + $siteHasECommerce[$idSite] = Piwik_Site::isEcommerceEnabledFor($idSite); + } + + $smarty = new Piwik_Smarty(); + $smarty->assign("isGoalPluginEnabled", $isGoalPluginEnabled); + $smarty->assign("reportRows", $dataRows); + $smarty->assign("reportRowsMetadata", $reportRowsMetadata); + $smarty->assign("prettyDate", $prettyDate); + $smarty->assign("siteHasECommerce", $siteHasECommerce); + + $this->rendering .= $smarty->fetch(PIWIK_USER_PATH . '/plugins/MobileMessaging/templates/SMSReport.tpl'); + } +} diff --git a/plugins/MobileMessaging/SMSProvider.php b/plugins/MobileMessaging/SMSProvider.php new file mode 100644 index 0000000000..01dda502b2 --- /dev/null +++ b/plugins/MobileMessaging/SMSProvider.php @@ -0,0 +1,81 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + * + * @category Piwik_Plugins + * @package Piwik_MobileMessaging + */ + +/** + * The Piwik_MobileMessaging_SMSProvider abstract class is used as a base class for SMS provider implementations. + * + * @package Piwik_MobileMessaging + * @subpackage Piwik_MobileMessaging_SMSProvider + */ +abstract class Piwik_MobileMessaging_SMSProvider +{ + static public $availableSMSProviders = array( + 'Mediaburst', + ); + + /** + * Return the SMSProvider associated to the provider name $providerName + * + * @throws exception If the provider is unknown + * @param string $providerName + * @return Piwik_MobileMessaging_SMSProvider + */ + static public function factory($providerName) + { + $name = ucfirst(strtolower($providerName)); + $className = 'Piwik_MobileMessaging_SMSProvider_' . $name; + + try { + Piwik_Loader::loadClass($className); + return new $className; + } catch(Exception $e) { + + @header('Content-Type: text/html; charset=utf-8'); + + throw new Exception( + Piwik_TranslateException( + 'MobileMessaging_Exception_UnknownProvider', + array($name, implode(', ', array_keys(self::$availableSMSProviders))) + ) + ); + } + } + + /** + * verify the SMS API credential + * + * @param string $username SMS API username + * @param string $password SMS API password + * @return bool true if SMS API credential are valid, false otherwise + */ + abstract public function verifyCredential($username, $password); + + /** + * get remaining credits + * + * @param string $username SMS API username + * @param string $password SMS API password + * @return string remaining credits + */ + abstract public function getCreditLeft($username, $password); + + /** + * send SMS + * + * @param string $username + * @param string $password + * @param string $smsText + * @param string $phoneNumber + * @return bool true + */ + abstract public function sendSMS($username, $password, $smsText, $phoneNumber, $from); +} diff --git a/plugins/MobileMessaging/SMSProvider/Mediaburst.php b/plugins/MobileMessaging/SMSProvider/Mediaburst.php new file mode 100644 index 0000000000..73665be93c --- /dev/null +++ b/plugins/MobileMessaging/SMSProvider/Mediaburst.php @@ -0,0 +1,105 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$$ + * + * @category Piwik_Plugins + * @package Piwik_MobileMessaging_SMSProvider + */ + +require_once PIWIK_INCLUDE_PATH . "/plugins/MobileMessaging/APIException.php"; +/** + * + * @package Piwik_MobileMessaging_SMSProvider + */ +class Piwik_MobileMessaging_SMSProvider_Mediaburst extends Piwik_MobileMessaging_SMSProvider +{ + const SOCKET_TIMEOUT = 10; + + const BASE_API_URL = 'https://api.mediaburst.co.uk/http'; + const CHECK_CREDIT_RESOURCE = '/credit.aspx'; + const SEND_SMS_RESOURCE = '/send.aspx'; + + const ERROR_STRING = 'Error'; + + const MAXIMUM_FROM_LENGTH = 11; + const MAXIMUM_CONTENT_LENGTH = 459; + + public function verifyCredential($username, $password) + { + $this->getCreditLeft($username, $password); + + return true; + } + + public function sendSMS($username, $password, $smsText, $phoneNumber, $from) + { + $from = substr($from, 0, self::MAXIMUM_FROM_LENGTH); + + //@review the length of the SMS text is limited by Mediaburst. Here, we truncate the SMS content because MultiSites.getAll can return a lot of sites. Is this the proper way to handle this case? + $smsText = substr($smsText, 0, self::MAXIMUM_CONTENT_LENGTH); + + $additionalParameters = array( + 'To' => str_replace('+','', $phoneNumber), + 'Content' => $smsText, + 'From' => $from, + 'Long' => 1, + ); + + $this->issueApiCall( + $username, + $password, + self::SEND_SMS_RESOURCE, + $additionalParameters + ); + } + + private function issueApiCall($username, $password, $resource, $additionalParameters = array()) + { + $accountParameters = array( + 'Username' => $username, + 'Password' => $password, + ); + + $parameters = array_merge($accountParameters, $additionalParameters); + + $url = self::BASE_API_URL + . $resource + . '?' . http_build_query($parameters, '', '&'); + + $timeout = self::SOCKET_TIMEOUT; + + $result = Piwik_Http::sendHttpRequestBy( + Piwik_Http::getTransportMethod(), + $url, + $timeout, + $userAgent = null, + $destinationPath = null, + $file = null, + $followDepth = 0, + $acceptLanguage = false, + $acceptInvalidSslCertificate = true + ); + + if(strpos($result, self::ERROR_STRING) !== false) + { + throw new Piwik_MobileMessaging_APIException( + 'Mediaburst API returned the following error message : ' . $result + ); + } + + return $result; + } + + public function getCreditLeft($username, $password) + { + return $this->issueApiCall( + $username, + $password, + self::CHECK_CREDIT_RESOURCE + ); + } +} diff --git a/plugins/MobileMessaging/images/phone.png b/plugins/MobileMessaging/images/phone.png Binary files differnew file mode 100644 index 0000000000..4b92a0b89e --- /dev/null +++ b/plugins/MobileMessaging/images/phone.png diff --git a/plugins/MobileMessaging/lang/en.php b/plugins/MobileMessaging/lang/en.php new file mode 100644 index 0000000000..5f14e7760f --- /dev/null +++ b/plugins/MobileMessaging/lang/en.php @@ -0,0 +1,38 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + * + * @category Piwik_Plugins + * @package Piwik_MobileMessaging + */ + +$translations = array( + 'MobileMessaging_Exception_UnknownProvider' => 'Provider name \'%s\' unknown. Try any of the following instead: %s.', + 'MobileMessaging_PluginDescription' => 'Create and download custom SMS reports and have them sent to your mobile daily, weekly or monthly.', + 'MobileMessaging_TopMenu' => 'Email & SMS Reports', + 'MobileMessaging_SettingsMenu' => 'Mobile Messaging', + 'MobileMessaging_VerificationText' => 'Code is %s. To validate your phone number and receive Piwik SMS reports please copy this code in the form accessible via Piwik > Settings > SMS.', + 'MobileMessaging_Settings_LetUsersManageAPICredential' => 'Allow users to manage their own SMS API credential', + 'MobileMessaging_Settings_SMSProvider' => 'SMS Provider', + 'MobileMessaging_Settings_SMSAPIAccount' => 'SMS API Account', + 'MobileMessaging_Settings_PhoneNumbers' => 'Phone Numbers', + 'MobileMessaging_Settings_UpdateAccount' => 'Update Account', + 'MobileMessaging_Settings_DeleteAccount' => 'Delete Account', + 'MobileMessaging_Settings_Username' => 'Username', + 'MobileMessaging_Settings_Password' => 'Password', + 'MobileMessaging_Settings_AddPhoneNumber' => 'Add', + 'MobileMessaging_Settings_ValidatePhoneNumber' => 'Validate', + 'MobileMessaging_Settings_RemovePhoneNumber' => 'Remove', + 'MobileMessaging_Settings_SuspiciousPhoneNumber' => 'If you don\'t receive the text message, you may try without the leading zero. ie. "53948298"', + 'MobileMessaging_Settings_CredentialNotProvidedByAdmin' => 'You can not configure phone numbers. The SMS API account has not been correctly configured. Please contact your administrator.', + 'MobileMessaging_Settings_CredentialNotProvided' => 'You can not configure phone numbers. The API username and password have not been correctly configured.', + 'MobileMessaging_Settings_CredentialProvided' => 'The %s SMS API account associated with username %s is correctly configured.', + 'MobileMessaging_MobileReport_PhoneNumbers' => 'Phone Numbers', + 'MobileMessaging_MobileReport_NoPhoneNumbers' => 'Please activate at least one phone number by accessing', + 'MobileMessaging_MobileReport_MobileMessagingSettingsLink' => 'the Mobile Messaging settings page', + 'MobileMessaging_MobileReport_AdditionalPhoneNumbers' => 'You can add more phone numbers by accessing', +); diff --git a/plugins/MobileMessaging/scripts/MobileMessagingSettings.js b/plugins/MobileMessaging/scripts/MobileMessagingSettings.js new file mode 100644 index 0000000000..47019f2f7f --- /dev/null +++ b/plugins/MobileMessaging/scripts/MobileMessagingSettings.js @@ -0,0 +1,234 @@ +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * @version $Id$ + */ + +// TODO when UI stabilized, factorize ajax boiler plate accros MobileMessagingSettings javascript functions +var MobileMessagingSettings = MobileMessagingSettings || (function () { + + /************************************************************ + * Private data + ************************************************************/ + var + delegatedManagementSelector = 'input[name=delegatedManagement]', + apiAccountSubmitSelector = '#apiAccountSubmit', + addPhoneNumberSubmitSelector = '#addPhoneNumberSubmit', + providersSelector = '#smsProviders', + usernameSelector = '#username', + passwordSelector = '#password', + countriesSelector = '#countries', + countryCallingCodeAttribute = 'calling-code', + countryCallingCodeSelector = '#countryCallingCode', + newPhoneNumberSelector = '#newPhoneNumber', + suspiciousPhoneNumberSelector = '#suspiciousPhoneNumber', + validatePhoneNumberSubmitSelector = '.validatePhoneNumberSubmit', + removePhoneNumberSubmitSelector = '.removePhoneNumberSubmit', + verificationCodeSelector = '.verificationCode', + phoneNumberSelector = '.phoneNumber', + deleteAcountSelector = '#deleteAccount', + ajaxErrorsSelector = 'ajaxErrorMobileMessagingSettings', + ajaxLoadingSelector = 'ajaxLoadingMobileMessagingSettings'; + + /************************************************************ + * Private methods + ************************************************************/ + + function initUIEvents() { + + $(delegatedManagementSelector).change(updateDelegatedManagement); + $(apiAccountSubmitSelector).click(updateApiAccount); + $(deleteAcountSelector).click(deleteApiAccount); + $(addPhoneNumberSubmitSelector).click(addPhoneNumber); + $(newPhoneNumberSelector).keyup(updateSuspiciousPhoneNumberMessage); + $(validatePhoneNumberSubmitSelector).click(validatePhoneNumber); + $(removePhoneNumberSubmitSelector).click(removePhoneNumber); + + var defaultCountry = getSelectedCountry(); + $(countriesSelector).selectToAutocomplete(); + // previous function seems to reset the pre-selected option + defaultCountry.attr('selected', 'selected'); + + $(countriesSelector).change(updateCountryCallingCode); + updateCountryCallingCode(); + } + + function validatePhoneNumber(event) + { + var phoneNumberContainer = $(event.target).parent(); + var verificationCode = phoneNumberContainer.find(verificationCodeSelector).val(); + var phoneNumber = phoneNumberContainer.find(phoneNumberSelector).html(); + + if(verificationCode != null && verificationCode != '') + { + var ajaxRequest = piwikHelper.getStandardAjaxConf(ajaxLoadingSelector, ajaxErrorsSelector); + + var parameters = {}; + ajaxRequest.data = parameters; + + parameters.module = 'API'; + parameters.format = 'json'; + parameters.method = 'MobileMessaging.validatePhoneNumber'; + parameters.phoneNumber = phoneNumber; + parameters.verificationCode = verificationCode; + parameters.token_auth = piwik.token_auth; + + $.ajax(ajaxRequest); + } + } + + function removePhoneNumber(event) + { + var phoneNumberContainer = $(event.target).parent(); + var phoneNumber = phoneNumberContainer.find(phoneNumberSelector).html(); + + var ajaxRequest = piwikHelper.getStandardAjaxConf(ajaxLoadingSelector, ajaxErrorsSelector); + + var parameters = {}; + ajaxRequest.data = parameters; + + parameters.module = 'API'; + parameters.format = 'json'; + parameters.method = 'MobileMessaging.removePhoneNumber'; + parameters.phoneNumber = phoneNumber; + parameters.token_auth = piwik.token_auth; + + $.ajax(ajaxRequest); + } + + function updateSuspiciousPhoneNumberMessage() + { + var newPhoneNumber = $(newPhoneNumberSelector).val(); + + // check if number starts with 0 + if(newPhoneNumber.lastIndexOf('0', 0) === 0) + { + $(suspiciousPhoneNumberSelector).show(); + } + else + { + $(suspiciousPhoneNumberSelector).hide(); + } + } + + function addPhoneNumber() + { + var newPhoneNumber = $(newPhoneNumberSelector).val(); + var countryCallingCode = $(countryCallingCodeSelector).val(); + + var phoneNumber = '+' + countryCallingCode + newPhoneNumber; + + if(phoneNumber != null && phoneNumber != '') + { + var ajaxRequest = piwikHelper.getStandardAjaxConf(ajaxLoadingSelector, ajaxErrorsSelector); + + var parameters = {}; + ajaxRequest.data = parameters; + + parameters.module = 'API'; + parameters.format = 'json'; + parameters.method = 'MobileMessaging.addPhoneNumber'; + parameters.phoneNumber = phoneNumber; + parameters.token_auth = piwik.token_auth; + + $.ajax(ajaxRequest); + } + } + + function getSelectedCountry() + { + return $(countriesSelector + ' option:selected'); + } + + function updateCountryCallingCode() + { + $(countryCallingCodeSelector).val( + getSelectedCountry().attr(countryCallingCodeAttribute) + ); + } + + function updateDelegatedManagement() { + setDelegatedManagement(getDelegatedManagement()); + } + + function deleteApiAccount() { + + var ajaxRequest = piwikHelper.getStandardAjaxConf(ajaxLoadingSelector, ajaxErrorsSelector); + + var parameters = {}; + ajaxRequest.data = parameters; + + parameters.module = 'API'; + parameters.format = 'json'; + parameters.method = 'MobileMessaging.deleteSMSAPICredential'; + parameters.token_auth = piwik.token_auth; + + $.ajax(ajaxRequest); + } + + function updateApiAccount() { + + var provider = $(providersSelector + ' option:selected').val(); + var username = $(usernameSelector).val(); + var password = $(passwordSelector).val(); + + if(username != '' && password != '') { + + var ajaxRequest = piwikHelper.getStandardAjaxConf(ajaxLoadingSelector, ajaxErrorsSelector); + + var parameters = {}; + ajaxRequest.data = parameters; + + parameters.module = 'API'; + parameters.format = 'json'; + parameters.method = 'MobileMessaging.setSMSAPICredential'; + parameters.provider = provider; + parameters.username = username; + parameters.password = password; + parameters.token_auth = piwik.token_auth; + + $.ajax(ajaxRequest); + } + } + + function setDelegatedManagement(delegatedManagement) { + + var ajaxRequest = piwikHelper.getStandardAjaxConf(ajaxLoadingSelector, ajaxErrorsSelector); + + var parameters = {}; + ajaxRequest.data = parameters; + + parameters.module = 'API'; + parameters.format = 'json'; + parameters.method = 'MobileMessaging.setDelegatedManagement'; + parameters.token_auth = piwik.token_auth; + parameters.delegatedManagement = delegatedManagement; + + $.ajax(ajaxRequest); + } + + function getDelegatedManagement() { + return $(delegatedManagementSelector + ':checked').val(); + } + + /************************************************************ + * Public data and methods + ************************************************************/ + + return { + + /* + * Initialize UI events + */ + initUIEvents: function () { + initUIEvents(); + } + }; + +}()); + +$(document).ready( function() { + MobileMessagingSettings.initUIEvents(); +}); diff --git a/plugins/MobileMessaging/scripts/jquery.select-to-autocomplete.js b/plugins/MobileMessaging/scripts/jquery.select-to-autocomplete.js new file mode 100644 index 0000000000..600e88eb87 --- /dev/null +++ b/plugins/MobileMessaging/scripts/jquery.select-to-autocomplete.js @@ -0,0 +1,283 @@ +/* +Version: 1.0.5 + +Documentation: http://baymard.com/labs/country-selector#documentation + +Copyright (C) 2011 by Jamie Appleseed, Baymard Institute (baymard.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +(function($){ + var settings = { + 'sort': false, + 'sort-attr': 'data-priority', + 'sort-desc': false, + 'autoselect': true, + 'alternative-spellings': true, + 'alternative-spellings-attr': 'data-alternative-spellings', + 'remove-valueless-options': true, + 'copy-attributes-to-text-field': true, + 'autocomplete-plugin': 'jquery_ui', + 'relevancy-sorting': true, + 'relevancy-sorting-partial-match-value': 1, + 'relevancy-sorting-strict-match-value': 5, + 'relevancy-sorting-booster-attr': 'data-relevancy-booster', + handle_invalid_input: function( context ) { + context.$text_field.val( context.$select_field.find('option:selected:first').text() ); + }, + handle_select_field: function( $select_field ) { + return $select_field.hide(); + }, + insert_text_field: function( context ) { + var $text_field = $( "<input></input>" ); + if ( settings['copy-attributes-to-text-field'] ) { + var attrs = {}; + var raw_attrs = context.$select_field[0].attributes; + for (var i=0; i < raw_attrs.length; i++) { + var key = raw_attrs[i].nodeName; + var value = raw_attrs[i].nodeValue; + if ( key !== 'name' && key !== 'id' ) { + attrs[key] = raw_attrs[i].nodeValue; + } + }; + $text_field.attr( attrs ); + } + $text_field.blur(function() { + var valid_values = context.$select_field.find('option').map(function(i, option) { return $(option).text(); }); + if ( !($text_field.val() in valid_values) && typeof settings['handle_invalid_input'] === 'function' ) { + settings['handle_invalid_input'](context); + } + }); + // give the input box the ability to select all text on mouse click + if ( context.settings['autoselect'] ) { + $text_field.click( function() { + this.select(); + }); + } + return $text_field.val( context.$select_field.find('option:selected:first').text() ) + .insertAfter( context.$select_field ); + }, + extract_options: function( $select_field ) { + var options = []; + var $options = $select_field.find('option'); + var number_of_options = $options.length; + + // go over each option in the select tag + $options.each(function(){ + var $option = $(this); + var option = { + 'real-value': $option.attr('value'), + 'label': $option.text() + } + if ( settings['remove-valueless-options'] && option['real-value'] === '') { + // skip options without a value + } else { + // prepare the 'matches' string which must be filtered on later + option['matches'] = option['label']; + var alternative_spellings = $option.attr( settings['alternative-spellings-attr'] ); + if ( alternative_spellings ) { + option['matches'] += ' ' + alternative_spellings; + } + // give each option a weight paramter for sorting + if ( settings['sort'] ) { + var weight = parseInt( $option.attr( settings['sort-attr'] ), 10 ); + if ( weight ) { + option['weight'] = weight; + } else { + option['weight'] = number_of_options; + } + } + // add relevancy score + if ( settings['relevancy-sorting'] ) { + option['relevancy-score'] = 0; + option['relevancy-score-booster'] = 1; + var boost_by = parseFloat( $option.attr( settings['relevancy-sorting-booster-attr'] ) ); + if ( boost_by ) { + option['relevancy-score-booster'] = boost_by; + } + } + // add option to combined array + options.push( option ); + } + }); + // sort the options based on weight + if ( settings['sort'] ) { + if ( settings['sort-desc'] ) { + options.sort( function( a, b ) { return b['weight'] - a['weight']; } ); + } else { + options.sort( function( a, b ) { return a['weight'] - b['weight']; } ); + } + } + + // return the set of options, each with the following attributes: real-value, label, matches, weight (optional) + return options; + } + }; + + var public_methods = { + init: function( customizations ) { + + if ( $.browser.msie && parseInt($.browser.version, 10) <= 7) { + + return this; + + } else { + + settings = $.extend( settings, customizations ); + + return this.each(function(){ + var $select_field = $(this); + + var context = { + '$select_field': $select_field, + 'options': settings['extract_options']( $select_field ), + 'settings': settings + }; + + context['$text_field'] = settings['insert_text_field']( context ); + + settings['handle_select_field']( $select_field ); + + if ( typeof settings['autocomplete-plugin'] === 'string' ) { + adapters[settings['autocomplete-plugin']]( context ); + } else { + settings['autocomplete-plugin']( context ); + } + }); + + } + + } + }; + + var adapters = { + jquery_ui: function( context ) { + // loose matching of search terms + var filter_options = function( term ) { + var split_term = term.split(' '); + var matchers = []; + for (var i=0; i < split_term.length; i++) { + if ( split_term[i].length > 0 ) { + var matcher = {}; + matcher['partial'] = new RegExp( $.ui.autocomplete.escapeRegex( split_term[i] ), "i" ); + if ( context.settings['relevancy-sorting'] ) { + matcher['strict'] = new RegExp( "^" + $.ui.autocomplete.escapeRegex( split_term[i] ), "i" ); + } + matchers.push( matcher ); + } + }; + + return $.grep( context.options, function( option ) { + var partial_matches = 0; + if ( context.settings['relevancy-sorting'] ) { + var strict_match = false; + var split_option_matches = option.matches.split(' '); + } + for ( var i=0; i < matchers.length; i++ ) { + if ( matchers[i]['partial'].test( option.matches ) ) { + partial_matches++; + } + if ( context.settings['relevancy-sorting'] ) { + for (var q=0; q < split_option_matches.length; q++) { + if ( matchers[i]['strict'].test( split_option_matches[q] ) ) { + strict_match = true; + break; + } + }; + } + }; + if ( context.settings['relevancy-sorting'] ) { + var option_score = 0; + option_score += partial_matches * context.settings['relevancy-sorting-partial-match-value']; + if ( strict_match ) { + option_score += context.settings['relevancy-sorting-strict-match-value']; + } + option_score = option_score * option['relevancy-score-booster']; + option['relevancy-score'] = option_score; + } + return (!term || matchers.length === partial_matches ); + }); + } + // update the select field value using either selected option or current input in the text field + var update_select_value = function( option ) { + if ( option ) { + if ( context.$select_field.val() !== option['real-value'] ) { + context.$select_field.val( option['real-value'] ); + context.$select_field.change(); + } + } else { + var option_name = context.$text_field.val().toLowerCase(); + var matching_option = { 'real-value': false }; + for (var i=0; i < context.options.length; i++) { + if ( option_name === context.options[i]['label'].toLowerCase() ) { + matching_option = context.options[i]; + break; + } + }; + if ( context.$select_field.val() !== matching_option['real-value'] ) { + context.$select_field.val( matching_option['real-value'] || '' ); + context.$select_field.change(); + } + if ( matching_option['real-value'] ) { + context.$text_field.val( matching_option['label'] ); + } + if ( typeof context.settings['handle_invalid_input'] === 'function' && context.$select_field.val() === '' ) { + context.settings['handle_invalid_input']( context ); + } + } + } + // jQuery UI autocomplete settings & behavior + context.$text_field.autocomplete({ + 'minLength': 0, + 'delay': 0, + 'autoFocus': true, + source: function( request, response ) { + var filtered_options = filter_options( request.term ); + if ( context.settings['relevancy-sorting'] ) { + filtered_options = filtered_options.sort( function( a, b ) { return b['relevancy-score'] - a['relevancy-score']; } ); + } + response( filtered_options ); + }, + select: function( event, ui ) { + update_select_value( ui.item ); + }, + change: function( event, ui ) { + update_select_value( ui.item ); + } + }); + // force refresh value of select field when form is submitted + context.$text_field.parents('form:first').submit(function(){ + update_select_value(); + }); + // select current value + update_select_value(); + } + }; + + $.fn.selectToAutocomplete = function( method ) { + if ( public_methods[method] ) { + return public_methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 )); + } else if ( typeof method === 'object' || ! method ) { + return public_methods.init.apply( this, arguments ); + } else { + $.error( 'Method ' + method + ' does not exist on jQuery.fn.selectToAutocomplete' ); + } + }; + +})(jQuery); diff --git a/plugins/MobileMessaging/templates/ReportParameters.tpl b/plugins/MobileMessaging/templates/ReportParameters.tpl new file mode 100644 index 0000000000..7a6af178f0 --- /dev/null +++ b/plugins/MobileMessaging/templates/ReportParameters.tpl @@ -0,0 +1,61 @@ +<script> + $(function() {ldelim} + resetReportParametersFunctions ['{$reportType}'] = + function () {ldelim} + + var reportParameters = {ldelim} + 'phoneNumbers' : [], + {rdelim}; + + updateReportParametersFunctions['{$reportType}'](reportParameters); + {rdelim}; + + updateReportParametersFunctions['{$reportType}'] = + function (reportParameters) {ldelim} + + if(reportParameters == null) return; + + $('[name=phoneNumbers]').removeProp('checked'); + $(reportParameters.phoneNumbers).each(function(index, phoneNumber) {ldelim} + $('#\\'+phoneNumber).prop('checked','checked'); + {rdelim}); + {rdelim}; + + getReportParametersFunctions['{$reportType}'] = + function () {ldelim} + + var parameters = Object(); + + var selectedPhoneNumbers = + $.map( + $('[name=phoneNumbers]:checked'), + function (phoneNumber) {ldelim} + return $(phoneNumber).attr('id'); + {rdelim} + ); + + // returning [''] when no phone numbers are selected avoids the "please provide a value for 'parameters'" error message + parameters.phoneNumbers = + selectedPhoneNumbers.length > 0 ? selectedPhoneNumbers : ['']; + + return parameters; + {rdelim}; + {rdelim}); +</script> + +<tr class='{$reportType}'> + <td class="first"> + {'MobileMessaging_MobileReport_PhoneNumbers'|translate} + </td> + <td> + {if $phoneNumbers|@count eq 0} + {'MobileMessaging_MobileReport_NoPhoneNumbers'|translate} + {else} + {foreach from=$phoneNumbers item=phoneNumber} + <input name='phoneNumbers' type='checkbox' id='{$phoneNumber}'/>{$phoneNumber}<br/> + {/foreach} + {'MobileMessaging_MobileReport_AdditionalPhoneNumbers'|translate} + {/if} + <a href='{url module="MobileMessaging"}'>{'MobileMessaging_MobileReport_MobileMessagingSettingsLink'|translate}</a> + </td> +</tr> diff --git a/plugins/MobileMessaging/templates/SMSReport.tpl b/plugins/MobileMessaging/templates/SMSReport.tpl new file mode 100644 index 0000000000..eb007bbe51 --- /dev/null +++ b/plugins/MobileMessaging/templates/SMSReport.tpl @@ -0,0 +1,67 @@ +{strip} + {$prettyDate}.{literal} {/literal} + {foreach name=reportRows from=$reportRows item=row key=rowId} + + {assign var=rowMetrics value=$row->getColumns()} + {assign var=rowMetadata value=$reportRowsMetadata[$rowId]->getColumns()} + + {*website name (if there is more than one site)*} + {if !$smarty.foreach.reportRows.first or !$smarty.foreach.reportRows.last} + {$rowMetrics.label}:{literal} {/literal} + {/if} + + {*visits*} + {$rowMetrics.nb_visits} {'General_ColumnNbVisits'|translate} + {if $rowMetrics.visits_evolution != 0} + {literal} {/literal}({$rowMetrics.visits_evolution}%) + {/if} + + {if $rowMetrics.nb_visits != 0} + + {*actions*} + ,{literal} {/literal} + {$rowMetrics.nb_actions} {'General_ColumnNbActions'|translate} + {if $rowMetrics.actions_evolution != 0} + {literal} {/literal}({$rowMetrics.actions_evolution}%) + {/if} + + {if $isGoalPluginEnabled} + + {*goal metrics*} + {if $rowMetrics.nb_conversions != 0} + + ,{literal} {/literal} + {'Goals_ColumnRevenue'|translate}:{literal} {/literal}{$rowMetrics.revenue} + {if $rowMetrics.revenue_evolution != 0} + {literal} {/literal}({$rowMetrics.revenue_evolution}%) + {/if} + + ,{literal} {/literal} + {$rowMetrics.nb_conversions} {'Goals_GoalConversions'|translate} + {if $rowMetrics.nb_conversions_evolution != 0} + {literal} {/literal}({$rowMetrics.nb_conversions_evolution}%) + {/if} + {/if} + + {*eCommerce metrics*} + {if $siteHasECommerce[$rowMetadata.idsite]} + + ,{literal} {/literal} + {'General_ProductRevenue'|translate}:{literal} {/literal}{$rowMetrics.ecommerce_revenue} + {if $rowMetrics.ecommerce_revenue_evolution != 0} + {literal} {/literal}({$rowMetrics.ecommerce_revenue_evolution}%) + {/if} + + ,{literal} {/literal} + {$rowMetrics.orders} {'General_EcommerceOrders'|translate} + {if $rowMetrics.orders_evolution != 0} + {literal} {/literal}({$rowMetrics.orders_evolution}%) + {/if} + {/if} + {/if} + + {/if} + + {if !$smarty.foreach.reportRows.last}.{literal} {/literal}{/if} + {/foreach} +{/strip} diff --git a/plugins/MobileMessaging/templates/Settings.tpl b/plugins/MobileMessaging/templates/Settings.tpl new file mode 100644 index 0000000000..3b97e3981c --- /dev/null +++ b/plugins/MobileMessaging/templates/Settings.tpl @@ -0,0 +1,112 @@ +{assign var=showSitesSelection value=false} +{assign var=showPeriodSelection value=false} +{include file='CoreAdminHome/templates/header.tpl'} +{loadJavascriptTranslations plugins='MobileMessaging'} + +{* TODO this UI probably needs some embellishment by working out a better HTML structure (table ? list ?), specifying better locations for errors and so on *} + +{if $isSuperUser} +<h2>{'MobileMessaging_Settings_LetUsersManageAPICredential'|translate}</h2> + +<input + type='radio' + value='true' + name='delegatedManagement'{if $delegatedManagement} checked='checked'{/if} /> +{'General_Yes'|translate} + +<input + type='radio' + value='false' + name='delegatedManagement'{if !$delegatedManagement} checked='checked'{/if} /> +{'General_No'|translate} + +{/if} + +{if $accountManagedByCurrentUser} +<h2>{'MobileMessaging_Settings_SMSAPIAccount'|translate}</h2> + {if $credentialSupplied} + {'MobileMessaging_Settings_CredentialProvided'|translate:$provider:$APIUsername} + {$creditLeft} + <a id='deleteAccount'>{'MobileMessaging_Settings_DeleteAccount'|translate}</a> + <br/> + {/if} + + {'MobileMessaging_Settings_UpdateAccount'|translate} : <br/> + + {'MobileMessaging_Settings_SMSProvider'|translate} + <select id="smsProviders"> + {foreach from=$smsProviders item=smsProvider} + <option value="{$smsProvider}"> + {$smsProvider} + </option> + {/foreach} + </select> + + {'MobileMessaging_Settings_Username'|translate} + <input size='25' id='username'/> + + {'MobileMessaging_Settings_Password'|translate} + <input size='25' id='password' type='password' autocomplete='off'/> + + <input type='submit' value='{'General_Save'|translate}' id='apiAccountSubmit' class='submit' /> +{/if} + +<h2>{'MobileMessaging_Settings_PhoneNumbers'|translate}</h2> +{if !$credentialSupplied} + {if $accountManagedByCurrentUser} + {'MobileMessaging_Settings_CredentialNotProvided'|translate} + {else} + {'MobileMessaging_Settings_CredentialNotProvidedByAdmin'|translate} + {/if} +{else} + <span id='suspiciousPhoneNumber' style='display:none;'> + {'MobileMessaging_Settings_SuspiciousPhoneNumber'|translate} + <br/> + </span> + <select id='countries'> + <option> </option> {* this is a trick to avoid selecting the first country when no default could be found *} + {foreach from=$countries key=countryCode item=country} + <option + value='{$countryCode}' + calling-code='{$country.countryCallingCode}' + {if $defaultCountry==$countryCode} selected='selected' {/if} + > + {$country.countryName} + </option> + {/foreach} + </select> + +<input id='countryCallingCode'/> + <input id='newPhoneNumber'/> + <input + type='submit' + value='{'MobileMessaging_Settings_AddPhoneNumber'|translate}' + id='addPhoneNumberSubmit' + class='submit' + /> + + <ul> + {foreach from=$phoneNumbers key=phoneNumber item=validated} + <li> + <span class='phoneNumber'>{$phoneNumber}</span> + {if !$validated} + <input class='verificationCode'/> + <input + type='submit' + value='{'MobileMessaging_Settings_ValidatePhoneNumber'|translate}' + class='submit validatePhoneNumberSubmit' + /> + {/if} + <input + type='submit' + value='{'MobileMessaging_Settings_RemovePhoneNumber'|translate}' + class='submit removePhoneNumberSubmit' + /> + </li> + {/foreach} + </ul> +{/if} + +{ajaxErrorDiv id=ajaxErrorMobileMessagingSettings} +{ajaxLoadingDiv id=ajaxLoadingMobileMessagingSettings} + +{include file='CoreAdminHome/templates/footer.tpl'} diff --git a/plugins/MultiSites/API.php b/plugins/MultiSites/API.php index 7a654ea706..bc87e2902b 100755 --- a/plugins/MultiSites/API.php +++ b/plugins/MultiSites/API.php @@ -20,6 +20,33 @@ require_once PIWIK_INCLUDE_PATH . '/plugins/MultiSites/CalculateEvolutionFilter. */ class Piwik_MultiSites_API { + const METRIC_TRANSLATION_KEY = 'translation'; + const METRIC_EVOLUTION_COL_NAME_KEY = 'evolution_column_name'; + const METRIC_RECORD_NAME_KEY = 'record_name'; + const METRIC_IS_ECOMMERCE_KEY = 'is_ecommerce'; + + const NB_VISITS_METRIC = 'nb_visits'; + const NB_ACTIONS_METRIC = 'nb_actions'; + const GOAL_REVENUE_METRIC = 'revenue'; + const GOAL_CONVERSION_METRIC = 'nb_conversions'; + const ECOMMERCE_ORDERS_METRIC = 'orders'; + const ECOMMERCE_REVENUE_METRIC = 'ecommerce_revenue'; + + static private $baseMetrics = array( + self::NB_VISITS_METRIC => array ( + self::METRIC_TRANSLATION_KEY => 'General_ColumnNbVisits', + self::METRIC_EVOLUTION_COL_NAME_KEY => 'visits_evolution', + self::METRIC_RECORD_NAME_KEY => self::NB_VISITS_METRIC, + self::METRIC_IS_ECOMMERCE_KEY => false, + ), + self::NB_ACTIONS_METRIC => array ( + self::METRIC_TRANSLATION_KEY => 'General_ColumnNbActions', + self::METRIC_EVOLUTION_COL_NAME_KEY => 'actions_evolution', + self::METRIC_RECORD_NAME_KEY => self::NB_ACTIONS_METRIC, + self::METRIC_IS_ECOMMERCE_KEY => false, + ), + ); + /** * The singleton instance of this class. */ @@ -42,28 +69,6 @@ class Piwik_MultiSites_API } /** - * Mapping of metric names to the names of their 'evolution' metric - * counterparts. Used by getAll. - */ - private $evolutionColumnNames = null; - - /** - * Constructor. - */ - function __construct() - { - $this->evolutionColumnNames = array( - 'nb_visits' => 'visits_evolution', - 'nb_actions' => 'actions_evolution' - ); - - if (Piwik_Common::isGoalPluginEnabled()) - { - $this->evolutionColumnNames[Piwik_Goals::getRecordName('revenue')] = 'revenue_evolution'; - } - } - - /** * Returns a report displaying the total visits, actions and revenue, as * well as the evolution of these values, of all existing sites over a * specified period of time. @@ -82,45 +87,107 @@ class Piwik_MultiSites_API * @param bool|string $segment The segments to get data for. * @param bool|string $_restrictSitesToLogin Hack used to enforce we restrict the returned data to the specified username * Only used when a scheduled task is running + * @param bool|string $enhanced When true, return additional goal & ecommerce metrics + * @return Piwik_DataTable + */ + public function getAll($period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false) + { + Piwik::checkUserHasSomeViewAccess(); + + return $this->buildDataTable( + 'all', + $period, + $date, + $segment, + $_restrictSitesToLogin, + $enhanced + ); + } + + /** + * Same as getAll but for a unique Piwik site + * @see Piwik_MultiSites_API::getAll() + * + * @param int $idSite Id of the Piwik site + * @param string $period The period type to get data for. + * @param string $date The date(s) to get data for. + * @param bool|string $segment The segments to get data for. + * @param bool|string $_restrictSitesToLogin Hack used to enforce we restrict the returned data to the specified username + * Only used when a scheduled task is running + * @param bool|string $enhanced When true, return additional goal & ecommerce metrics * @return Piwik_DataTable */ - public function getAll($period, $date, $segment = false, $_restrictSitesToLogin = false) + public function getOne($idSite, $period, $date, $segment = false, $_restrictSitesToLogin = false, $enhanced = false) { Piwik::checkUserHasSomeViewAccess(); - $isGoalPluginEnabled = Piwik_Common::isGoalPluginEnabled(); - - - if (Piwik::isUserIsSuperUser() - // Hack: when this API function is called as a Scheduled Task, Super User status is enforced. - // This means this function would return ALL websites in all cases. - // Instead, we make sure that only the right set of data is returned - && !Piwik_TaskScheduler::isTaskBeingExecuted()) + + return $this->buildDataTable( + $idSite, + $period, + $date, + $segment, + $_restrictSitesToLogin, + $enhanced + ); + } + + private function buildDataTable($sites, $period, $date, $segment, $_restrictSitesToLogin, $enhanced) + { + $allWebsitesRequested = $sites == 'all'; + if($allWebsitesRequested) { - $sites = Piwik_SitesManager_API::getInstance()->getAllSites(); - Piwik_Site::setSites($sites); - } - else - { - $sites = Piwik_SitesManager_API::getInstance()->getSitesWithAtLeastViewAccess($limit = false, $_restrictSitesToLogin); - Piwik_Site::setSitesFromArray($sites); + if (Piwik::isUserIsSuperUser() + // Hack: when this API function is called as a Scheduled Task, Super User status is enforced. + // This means this function would return ALL websites in all cases. + // Instead, we make sure that only the right set of data is returned + && !Piwik_TaskScheduler::isTaskBeingExecuted()) + { + Piwik_Site::setSites( + Piwik_SitesManager_API::getInstance()->getAllSites() + ); + } + else + { + Piwik_Site::setSitesFromArray( + Piwik_SitesManager_API::getInstance()->getSitesWithAtLeastViewAccess($limit = false, $_restrictSitesToLogin) + ); + } } - + // build the archive type used to query archive data - $archive = Piwik_Archive::build('all', $period, $date, $segment, $_restrictSitesToLogin); + $archive = Piwik_Archive::build( + $sites, + $period, + $date, + $segment, + $_restrictSitesToLogin + ); // determine what data will be displayed - $fieldsToGet = array('nb_visits', 'nb_actions'); - if ($isGoalPluginEnabled) + $fieldsToGet = array(); + $columnNameRewrites = array(); + $apiECommerceMetrics = array(); + $apiMetrics = Piwik_MultiSites_API::getApiMetrics($enhanced); + foreach($apiMetrics as $metricName => $metricSettings) { - $revenueMetric = Piwik_Goals::getRecordName('revenue'); - $fieldsToGet[] = $revenueMetric; + $fieldsToGet[] = $metricSettings[self::METRIC_RECORD_NAME_KEY]; + $columnNameRewrites[$metricSettings[self::METRIC_RECORD_NAME_KEY]] = $metricName; + + if($metricSettings[self::METRIC_IS_ECOMMERCE_KEY]) + { + $apiECommerceMetrics[$metricName] = $metricSettings; + } } - + // get the data + // $dataTable instanceOf Piwik_DataTable_Array $dataTable = $archive->getDataTableFromNumeric($fieldsToGet); // get rid of the DataTable_Array that is created by the IndexedBySite archive type - $dataTable = $dataTable->mergeChildren(); + if($dataTable instanceof Piwik_DataTable_Array && $allWebsitesRequested) + { + $dataTable = $dataTable->mergeChildren(); + } // if the period isn't a range & a lastN/previousN date isn't used, we get the same // data for the last period to show the evolution of visits/actions/revenue @@ -129,10 +196,10 @@ class Piwik_MultiSites_API if (strpos($date, ',')) // date in the form of 2011-01-01,2011-02-02 { $rangePeriod = new Piwik_Period_Range($period, $date); - + $lastStartDate = Piwik_Period_Range::removePeriod($period, $rangePeriod->getDateStart(), $n = 1); $lastEndDate = Piwik_Period_Range::removePeriod($period, $rangePeriod->getDateEnd(), $n = 1); - + $strLastDate = "$lastStartDate,$lastEndDate"; } else @@ -146,31 +213,60 @@ class Piwik_MultiSites_API $pastData = $pastData->mergeChildren(); // use past data to calculate evolution percentages - $this->calculateEvolutionPercentages($dataTable, $pastData, $fieldsToGet); + $this->calculateEvolutionPercentages($dataTable, $pastData, $apiMetrics); + } + + // remove eCommerce related metrics on non eCommerce Piwik sites + // @review this is not optimal in terms of performance: those metrics should not be retrieved in the first place + if($enhanced) + { + // $dataTableRows instanceOf Piwik_DataTable_Row[] + $dataTableRows = $dataTable->getRows(); + + foreach($dataTableRows as $dataTableRow) + { + $siteId = $dataTableRow->getColumn('label'); + if(!Piwik_Site::isEcommerceEnabledFor($siteId)) + { + foreach($apiECommerceMetrics as $metricSettings) + { + $dataTableRow->deleteColumn($metricSettings[self::METRIC_RECORD_NAME_KEY]); + $dataTableRow->deleteColumn($metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY]); + } + } + } } // move the site id to a metadata column $dataTable->filter('ColumnCallbackAddMetadata', array('label', 'idsite')); // set the label of each row to the site name - $getNameFor = array('Piwik_Site', 'getNameFor'); - $dataTable->filter('ColumnCallbackReplace', array('label', $getNameFor)); - - // rename the revenue column from the metric name to 'revenue' - if ($isGoalPluginEnabled) + if($allWebsitesRequested) { - $mapping = array($revenueMetric => 'revenue'); - $dataTable->filter('ReplaceColumnNames', array($mapping)); + $getNameFor = array('Piwik_Site', 'getNameFor'); + $dataTable->filter('ColumnCallbackReplace', array('label', $getNameFor)); } - + + // replace record names with user friendly metric names + $dataTable->filter('ReplaceColumnNames', array($columnNameRewrites)); + // Ensures data set sorted, for Metadata output - $dataTable->filter('Sort', array('nb_visits', 'desc', $naturalSort = false)); + $dataTable->filter('Sort', array(self::NB_VISITS_METRIC, 'desc', $naturalSort = false)); + + // filter rows without visits + $dataTable->filter( + 'ColumnCallbackDeleteRow', + array( + self::NB_VISITS_METRIC, + create_function ( '$value', 'return $value != 0;') + ) + ); return $dataTable; } /** - * Utility function used by getAll. Performs a binary filter of two + * Performs a binary filter of two * DataTables in order to correctly calculate evolution metrics. * * @param Piwik_DataTable|Piwik_DataTable_Array $currentData @@ -178,24 +274,77 @@ class Piwik_MultiSites_API * @param array $fields The array of string fields to calculate evolution * metrics for. */ - private function calculateEvolutionPercentages($currentData, $pastData, $fields) + private function calculateEvolutionPercentages($currentData, $pastData, $apiMetrics) { if ($currentData instanceof Piwik_DataTable_Array) { $pastArray = $pastData->getArray(); - foreach ($currentData->getArray() as $label => $subTable) + foreach ($currentData->getArray() as $subTable) { - $this->calculateEvolutionPercentages($subTable, current($pastArray), $fields); + $this->calculateEvolutionPercentages($subTable, current($pastArray), $apiMetrics); next($pastArray); } } else { - foreach ($fields as $field) + foreach ($apiMetrics as $metricSettings) + { + $currentData->filter( + 'Piwik_MultiSites_CalculateEvolutionFilter', + array( + $pastData, + $metricSettings[self::METRIC_EVOLUTION_COL_NAME_KEY], + $metricSettings[self::METRIC_RECORD_NAME_KEY], + $quotientPrecision = 2) + ); + } + } + } + + /** + * @ignore + */ + public static function getApiMetrics($enhanced) + { + $metrics = self::$baseMetrics; + if (Piwik_Common::isGoalPluginEnabled()) + { + // goal revenue metric + $metrics[self::GOAL_REVENUE_METRIC] = array( + self::METRIC_TRANSLATION_KEY => 'Goals_ColumnRevenue', + self::METRIC_EVOLUTION_COL_NAME_KEY => self::GOAL_REVENUE_METRIC . '_evolution', + self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC), + self::METRIC_IS_ECOMMERCE_KEY => false, + ); + + if($enhanced) { - $currentData->filter('Piwik_MultiSites_CalculateEvolutionFilter', - array($pastData, $this->evolutionColumnNames[$field], $field, $quotientPrecision = 2)); + // number of goal conversions metric + $metrics[self::GOAL_CONVERSION_METRIC] = array( + self::METRIC_TRANSLATION_KEY => 'Goals_ColumnConversions', + self::METRIC_EVOLUTION_COL_NAME_KEY => self::GOAL_CONVERSION_METRIC . '_evolution', + self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_CONVERSION_METRIC), + self::METRIC_IS_ECOMMERCE_KEY => false, + ); + + // number of orders + $metrics[self::ECOMMERCE_ORDERS_METRIC] = array( + self::METRIC_TRANSLATION_KEY => 'General_EcommerceOrders', + self::METRIC_EVOLUTION_COL_NAME_KEY => self::ECOMMERCE_ORDERS_METRIC . '_evolution', + self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_CONVERSION_METRIC ,0), + self::METRIC_IS_ECOMMERCE_KEY => true, + ); + + // eCommerce revenue + $metrics[self::ECOMMERCE_REVENUE_METRIC] = array( + self::METRIC_TRANSLATION_KEY => 'General_ProductRevenue', + self::METRIC_EVOLUTION_COL_NAME_KEY => self::ECOMMERCE_REVENUE_METRIC . '_evolution', + self::METRIC_RECORD_NAME_KEY => Piwik_Goals::getRecordName(self::GOAL_REVENUE_METRIC ,0), + self::METRIC_IS_ECOMMERCE_KEY => true, + ); } } + + return $metrics; } } diff --git a/plugins/MultiSites/MultiSites.php b/plugins/MultiSites/MultiSites.php index df7472e714..b12c4bfc1a 100644 --- a/plugins/MultiSites/MultiSites.php +++ b/plugins/MultiSites/MultiSites.php @@ -41,22 +41,36 @@ class Piwik_MultiSites extends Piwik_Plugin */ public function getReportMetadata($notification) { - $isGoalPluginEnabled = Piwik_Common::isGoalPluginEnabled(); - - $metrics = array( 'nb_visits', 'nb_actions' ); - if ($isGoalPluginEnabled) + $metadataMetrics = array(); + foreach(Piwik_MultiSites_API::getApiMetrics($enhanced = true) as $metricName => $metricSettings) { - $metrics['revenue'] = Piwik_Translate('Goals_ColumnRevenue'); + $metadataMetrics[$metricName] = + Piwik_Translate($metricSettings[Piwik_MultiSites_API::METRIC_TRANSLATION_KEY]); + $metadataMetrics[$metricSettings[Piwik_MultiSites_API::METRIC_EVOLUTION_COL_NAME_KEY]] = + Piwik_Translate($metricSettings[Piwik_MultiSites_API::METRIC_TRANSLATION_KEY]) . " " . Piwik_Translate('MultiSites_Evolution'); } $reports = &$notification->getNotificationObject(); + $reports[] = array( 'category' => Piwik_Translate('General_MultiSitesSummary'), 'name' => Piwik_Translate('General_AllWebsitesDashboard'), 'module' => 'MultiSites', 'action' => 'getAll', 'dimension' => Piwik_Translate('General_Website'), // re-using translation - 'metrics' => $metrics, + 'metrics' => $metadataMetrics, + 'processedMetrics' => false, + 'constantRowsCount' => false, + 'order' => 5 + ); + + $reports[] = array( + 'category' => Piwik_Translate('General_MultiSitesSummary'), + 'name' => Piwik_Translate('General_SingleWebsitesDashboard'), + 'module' => 'MultiSites', + 'action' => 'getOne', + 'dimension' => Piwik_Translate('General_Website'), // re-using translation + 'metrics' => $metadataMetrics, 'processedMetrics' => false, 'constantRowsCount' => false, 'order' => 5 diff --git a/plugins/PDFReports/API.php b/plugins/PDFReports/API.php index 52bbffcf58..8760f47e3c 100644 --- a/plugins/PDFReports/API.php +++ b/plugins/PDFReports/API.php @@ -24,10 +24,31 @@ */ class Piwik_PDFReports_API { + const VALIDATE_PARAMETERS_EVENT = 'PDFReports.validateReportParameters'; + const GET_REPORT_PARAMETERS_EVENT = 'PDFReports.getReportParameters'; + const GET_REPORT_METADATA_EVENT = 'PDFReports.getReportMetadata'; + const GET_REPORT_TYPES_EVENT = 'PDFReports.getReportTypes'; + const GET_REPORT_FORMATS_EVENT = 'PDFReports.getReportFormats'; + const GET_RENDERER_INSTANCE_EVENT = 'PDFReports.getRendererInstance'; + const PROCESS_REPORTS_EVENT = 'PDFReports.processReports'; + const GET_REPORT_RECIPIENTS_EVENT = 'PDFReports.getReportRecipients'; + const ALLOW_MULTIPLE_REPORTS_EVENT = 'PDFReports.allowMultipleReports'; + const SEND_REPORT_EVENT = 'PDFReports.sendReport'; + const OUTPUT_DOWNLOAD = 1; const OUTPUT_SAVE_ON_DISK = 2; - protected $reportsMetadata = array(); + const REPORT_TYPE_INFO_KEY = 'reportType'; + const ID_SITE_INFO_KEY = 'idSite'; + const REPORT_KEY = 'report'; + const REPORT_CONTENT_KEY = 'contents'; + const FILENAME_KEY = 'filename'; + const PRETTY_DATE_KEY = 'prettyDate'; + const WEBSITE_NAME_KEY = 'websiteName'; + const ADDITIONAL_FILES_KEY = 'additionalFiles'; + + const REPORT_TRUNCATE = 23; + static private $instance = null; /** @@ -48,57 +69,61 @@ class Piwik_PDFReports_API * @param int $idSite * @param string $description Report description * @param string $period Schedule frequency: day, week or month - * @param string $reportFormat 'pdf' or 'html' - * @return int $displayFormat see Piwik_PDFReports_API::getDisplayFormats() - * @param string $reports Comma separated list of reports - * @param bool $emailMe - * @param string $additionalEmails Comma separated list of emails + * @param string $reportType 'email' or any other format provided via the PDFReports.getReportTypes hook + * @param string $reportFormat 'pdf', 'html' or any other format provided via the PDFReports.getReportFormats hook + * @param string $reports JSON array of reports + * @param string $parameters JSON encoded parameters * * @return int idReport generated */ - public function addReport( $idSite, $description, $period, $reportFormat, $displayFormat, $reports, $emailMe = true, $additionalEmails = false) + public function addReport($idSite, $description, $period, $reportType, $reportFormat, $reports, $parameters) { Piwik::checkUserIsNotAnonymous(); Piwik::checkUserHasViewAccess($idSite); - $this->checkPeriod($period); - $this->checkFormat($reportFormat); - $this->checkDisplayFormat($displayFormat); - $description = $this->checkDescription($description); $currentUser = Piwik::getCurrentUserLogin(); - $emailMe = (int)$emailMe; - - $this->ensureLanguageSetForUser($currentUser); + self::ensureLanguageSetForUser($currentUser); - $additionalEmails = $this->checkAdditionalEmails($additionalEmails); - $reports = $this->checkAvailableReports($idSite, $reports); + // common validations + self::validateReportPeriod($period); + $description = self::validateDescription($description); + self::validateReportType($reportType); + self::validateReportFormat($reportType, $reportFormat); + + // report parameters validations + $parameters = self::validateReportParameters($reportType, $parameters); + + // validation of requested reports + $reports = self::validateRequestedReports($idSite, $reportType, $reports); $db = Zend_Registry::get('db'); - $idReport = $db->fetchOne("SELECT max(idreport) + 1 - FROM ".Piwik_Common::prefixTable('pdf')); + // there must be something better than this to generate a new id.. + $idReport = $db->fetchOne("SELECT max(idreport) + 1 FROM ".Piwik_Common::prefixTable('report')); + if($idReport == false) { $idReport = 1; } - $db->insert(Piwik_Common::prefixTable('pdf'), + + $db->insert(Piwik_Common::prefixTable('report'), array( 'idreport' => $idReport, 'idsite' => $idSite, 'login' => $currentUser, 'description' => $description, 'period' => $period, + 'type' => $reportType, 'format' => $reportFormat, - 'display_format' => $displayFormat, - 'email_me' => $emailMe, - 'additional_emails' => $additionalEmails, + 'parameters' => $parameters, 'reports' => $reports, 'ts_created' => Piwik_Date::now()->getDatetime(), 'deleted' => 0, )); + return $idReport; } - private function ensureLanguageSetForUser($currentUser) + private static function ensureLanguageSetForUser($currentUser) { $lang = Piwik_LanguagesManager_API::getInstance()->getLanguageForUser( $currentUser ); if(empty($lang)) @@ -112,7 +137,7 @@ class Piwik_PDFReports_API * * @see addReport() */ - public function updateReport( $idReport, $idSite, $description, $period, $reportFormat, $displayFormat, $reports, $emailMe = true, $additionalEmails = false) + public function updateReport( $idReport, $idSite, $description, $period, $reportType, $reportFormat, $reports, $parameters) { Piwik::checkUserIsNotAnonymous(); Piwik::checkUserHasViewAccess($idSite); @@ -120,32 +145,34 @@ class Piwik_PDFReports_API $pdfReports = $this->getReports($idSite, $periodSearch = false, $idReport); $report = reset($pdfReports); $idReport = $report['idreport']; - - $this->checkPeriod($period); - $this->checkFormat($reportFormat); - $this->checkDisplayFormat($displayFormat); - $description = $this->checkDescription($description); + $currentUser = Piwik::getCurrentUserLogin(); - $emailMe = (int)$emailMe; - - $this->ensureLanguageSetForUser($currentUser); - - $additionalEmails = $this->checkAdditionalEmails($additionalEmails); - - $reports = $this->checkAvailableReports($idSite, $reports); + self::ensureLanguageSetForUser($currentUser); + + // common validations + self::validateReportPeriod($period); + $description = self::validateDescription($description); + self::validateReportType($reportType); + self::validateReportFormat($reportType, $reportFormat); + + // report parameters validations + $parameters = self::validateReportParameters($reportType, $parameters); + + // validation of requested reports + $reports = self::validateRequestedReports($idSite, $reportType, $reports); - Zend_Registry::get('db')->update( Piwik_Common::prefixTable('pdf'), + Zend_Registry::get('db')->update( Piwik_Common::prefixTable('report'), array( 'description' => $description, 'period' => $period, + 'type' => $reportType, 'format' => $reportFormat, - 'display_format' => $displayFormat, - 'email_me' => $emailMe, - 'additional_emails' => $additionalEmails, + 'parameters' => $parameters, 'reports' => $reports, ), "idreport = '$idReport'" - ); + ); + self::$cache = array(); } @@ -160,7 +187,7 @@ class Piwik_PDFReports_API $report = reset($pdfReports); Piwik::checkUserIsSuperUserOrTheUser($report['login']); - Zend_Registry::get('db')->update( Piwik_Common::prefixTable('pdf'), + Zend_Registry::get('db')->update( Piwik_Common::prefixTable('report'), array( 'deleted' => 1, ), @@ -204,7 +231,7 @@ class Piwik_PDFReports_API if(!empty($period)) { - $this->checkPeriod($period); + $this->validateReportPeriod($period); $sqlWhere .= " AND period = ? "; $bind[] = $period; } @@ -222,7 +249,7 @@ class Piwik_PDFReports_API // Joining with the site table to work around pre-1.3 where reports could still be linked to a deleted site $reports = Piwik_FetchAll("SELECT * - FROM ".Piwik_Common::prefixTable('pdf')." + FROM ".Piwik_Common::prefixTable('report')." JOIN ".Piwik_Common::prefixTable('site')." USING (idsite) WHERE deleted = 0 @@ -233,138 +260,105 @@ class Piwik_PDFReports_API { throw new Exception("Requested report couldn't be found."); } + + foreach($reports as &$report) { + // decode report parameters + $report['parameters'] = json_decode($report['parameters'], true); + + // decode report list + $report['reports'] = json_decode($report['reports'], true); + } + // static cache self::$cache[$cacheKey] = $reports; return $reports; } - - /** + + /** * Generates a report file. * - * @param int $idReport ID of the report to generate. If idReport=0 it will generate a report containing all reports - * for the specified period & date - * @param string $date YYYY-MM-DD - * @param bool|false|int $idSite + * @param int $idReport ID of the report to generate. + * @param string $date YYYY-MM-DD * @param bool|false|string $language If not passed, will use default language. * @param bool|false|int $outputType 1 = download report, 2 = save report to disk, defaults to download * @param bool|false|string $period Defaults to 'day'. If not specified, will default to the report's period set when creating the report - * @param bool|string $reportFormat pdf, html - * @param bool|false|int $displayFormat see Piwik_PDFReports_API::getDisplayFormats() - * @return array + * @param bool|false|string $reportFormat 'pdf', 'html' or any other format provided via the PDFReports.getReportFormats hook + * @param bool|false|string $parameters JSON encoded parameters + * @return array|void */ - public function generateReport($idReport, $date, $idSite = false, $language = false, $outputType = false, $period = false, $reportFormat = false, $displayFormat = false) + public function generateReport($idReport, $date, $language = false, $outputType = false, $period = false, $reportFormat = false, $parameters = false) { Piwik::checkUserIsNotAnonymous(); - // Load specified language + // load specified language if(empty($language)) { $language = Piwik_Translate::getInstance()->getLanguageDefault(); } + Piwik_Translate::getInstance()->reloadLanguage($language); - // Available reports - $reportMetadata = Piwik_API_API::getInstance()->getReportMetadata($idSite); + $reports = $this->getReports($idSite = false, $_period = false, $idReport); + $report = reset($reports); - // User who created this report - $userLogin = false; - - // Test template: include all reports - if($idReport == 0) - { - if(empty($period)) - { - $period = 'day'; - } - if(empty($reportFormat)) - { - $reportFormat = Piwik_PDFReports::DEFAULT_FORMAT; - } - if(empty($displayFormat)) - { - $displayFormat = Piwik_PDFReports::DEFAULT_DISPLAY_FORMAT; - } + $idSite = $report['idsite']; + $reportType = $report['type']; - $reports = array(); - foreach($reportMetadata as $report) - { - if($report['category'] != 'API') - { - $reports[] = $report; - } - } + // override report period + if(empty($period)) + { + $period = $report['period']; + } - $description = Piwik_Translate('PDFReports_DefaultContainingAllReports'); + // override report format + if(!empty($reportFormat)) + { + self::validateReportFormat($reportType, $reportFormat); + $report['format'] = $reportFormat; } - // Template is a custom template else { - $pdfReports = $this->getReports($idSite, $_period = false, $idReport); - $pdfReport = reset($pdfReports); - - $userLogin = $pdfReport['login']; - $reportUniqueIds = explode(',', $pdfReport['reports']); - - $description = $pdfReport['description']; + $reportFormat = $report['format']; + } - // If period wasn't specified, we shall default to the report's period - if(empty($period)) - { - $period = 'day'; - if($pdfReport['period'] != 'never') - { - $period = $pdfReport['period']; - } - } + // override report parameters + if(!empty($parameters)) + { + $report['parameters'] = json_decode( + self::validateReportParameters($reportType, $parameters), + true + ); + } + else + { + $parameters = $report['parameters']; + } - // If format wasn't specified, defaults to the report's format - if(empty($reportFormat)) - { - $reportFormat = $pdfReport['format']; - // Handle cases for reports created before the 'format' field - if(empty($reportFormat)) - { - $reportFormat = Piwik_PDFReports::DEFAULT_FORMAT; - } - } + // decode report list + $reportUniqueIds = $report['reports']; - // If $displayFormat wasn't specified, defaults to the report configuration - if(empty($displayFormat)) - { - $displayFormat = $pdfReport['display_format']; - } + // available reports + $availableReportMetadata = Piwik_API_API::getInstance()->getReportMetadata($idSite); - // We need to lookup which reports metadata are registered in this report - $reports = array(); - foreach($reportMetadata as $metadata) + // we need to lookup which reports metadata are registered in this report + $reportMetadata = array(); + foreach($availableReportMetadata as $metadata) + { + if(in_array($metadata['uniqueId'], $reportUniqueIds)) { - if(in_array($metadata['uniqueId'], $reportUniqueIds)) - { - $reports[] = $metadata; - } + $reportMetadata[] = $metadata; } - } - // prepare the report renderer - $reportRenderer = Piwik_ReportRenderer::factory($reportFormat); - $reportRenderer->setLocale($language); - $reportRenderer->setRenderImageInline($outputType == self::OUTPUT_DOWNLOAD ? true : false); - - $description = str_replace(array("\r", "\n"), ' ', $description); - - // The report will be rendered with the first 23 rows and will aggregate other rows in a summary row - $filterTruncateGET = Piwik_Common::getRequestVar('filter_truncate', false); - + // the report will be rendered with the first 23 rows and will aggregate other rows in a summary row // 23 rows table fits in one portrait page - $reportTruncation = 23; - $_GET['filter_truncate'] = $reportTruncation; + $initialFilterTruncate = Piwik_Common::getRequestVar('filter_truncate', false); + $_GET['filter_truncate'] = self::REPORT_TRUNCATE; - $websiteName = $prettyDate = false; + $prettyDate = null; $processedReports = array(); - - foreach ($reports as $action) + foreach ($reportMetadata as $action) { $apiModule = $action['module']; $apiAction = $action['action']; @@ -373,67 +367,91 @@ class Piwik_PDFReports_API { $apiParameters = $action['parameters']; } - + $mustRestoreGET = false; - - // All Websites dashboard should not be truncated in the report - if($apiModule == 'MultiSites' && $apiAction == 'getAll') + + // all Websites dashboard should not be truncated in the report + if($apiModule == 'MultiSites') { $mustRestoreGET = $_GET; - $_GET['filter_truncate'] = false; - - // When a view/admin user created a report, workaround the fact that "Super User" - // is enforced in Scheduled tasks, and ensure Multisites.getAll only return the websites that this user can access - if(!empty($userLogin) - && $userLogin != Piwik_Config::getInstance()->superuser['login']) + $_GET['enhanced'] = true; + + if($apiAction == 'getAll') { - $_GET['_restrictSitesToLogin'] = $userLogin; + $_GET['filter_truncate'] = false; + + // when a view/admin user created a report, workaround the fact that "Super User" + // is enforced in Scheduled tasks, and ensure Multisites.getAll only return the websites that this user can access + if(!empty($userLogin) + && $userLogin != Piwik_Config::getInstance()->superuser['login']) + { + $_GET['_restrictSitesToLogin'] = $userLogin; + } } } - $report = Piwik_API_API::getInstance()->getProcessedReport($idSite, $period, $date, $apiModule, $apiAction, $segment = false, $apiParameters, $idGoal = false, $language); - + + $processedReport = Piwik_API_API::getInstance()->getProcessedReport( + $idSite, $period, $date, $apiModule, $apiAction, + $segment = false, $apiParameters, $idGoal = false, $language + ); + + // TODO add static method getPrettyDate($period, $date) in Piwik_Period + $prettyDate = $processedReport['prettyDate']; + if($mustRestoreGET) { $_GET = $mustRestoreGET; } - $websiteName = $report['website']; - $prettyDate = $report['prettyDate']; - - $reportMetadata = $report['metadata']; - $isAggregateReport = !empty($reportMetadata['dimension']); - - $report['displayTable'] = $displayFormat != Piwik_PDFReports::DISPLAY_FORMAT_GRAPHS_ONLY; - $report['displayGraph'] = - ($isAggregateReport ? - $displayFormat == Piwik_PDFReports::DISPLAY_FORMAT_GRAPHS_ONLY || $displayFormat == Piwik_PDFReports::DISPLAY_FORMAT_TABLES_AND_GRAPHS - : - $displayFormat != Piwik_PDFReports::DISPLAY_FORMAT_TABLES_ONLY) - && Piwik::isGdExtensionEnabled() - && Piwik_PluginsManager::getInstance()->isPluginActivated('ImageGraph') - && !empty($reportMetadata['imageGraphUrl']); - - $processedReports[] = $report; + $processedReports[] = $processedReport; } - // Restore values - if($filterTruncateGET !== false) + // restore filter truncate parameter value + if($initialFilterTruncate !== false) { - $_GET['filter_truncate'] = $filterTruncateGET; + $_GET['filter_truncate'] = $initialFilterTruncate; } - // generate the report - $reportRenderer->renderFrontPage($websiteName, $prettyDate, $description, $reports ); + $notificationInfo = array( + self::REPORT_TYPE_INFO_KEY => $reportType, + self::REPORT_KEY => $report, + ); + + // allow plugins to alter processed reports + Piwik_PostEvent( + self::PROCESS_REPORTS_EVENT, + $processedReports, + $notificationInfo + ); + + // retrieve report renderer instance + $reportRenderer = null; + Piwik_PostEvent( + self::GET_RENDERER_INSTANCE_EVENT, + $reportRenderer, + $notificationInfo + ); + + // init report renderer + $reportRenderer->setLocale($language); + $reportRenderer->setRenderImageInline($outputType == self::OUTPUT_DOWNLOAD ? true : false); + + // render report + $site = Piwik_SitesManager_API::getInstance()->getSiteFromId($idSite); + $websiteName = $site['name']; + $description = str_replace(array("\r", "\n"), ' ', $report['description']); + + $reportRenderer->renderFrontPage($websiteName, $prettyDate, $description, $reportMetadata); array_walk($processedReports, array($reportRenderer, 'renderReport')); switch($outputType) { case self::OUTPUT_SAVE_ON_DISK: - $outputFilename = 'Email Report - ' . $idReport . '.' . $date . '.' . $idSite . '.' . $language; + $outputFilename = strtoupper($reportFormat) . ' ' . ucfirst($reportType) .' Report - ' . $idReport . '.' . $date . '.' . $idSite . '.' . $language; $outputFilename = $reportRenderer->sendToDisk($outputFilename); $additionalFiles = array(); - if($reportFormat == 'html') + if($reportRenderer instanceof Piwik_ReportRenderer_Html) { foreach ($processedReports as &$report) { if($report['displayGraph']) @@ -455,11 +473,11 @@ class Piwik_PDFReports_API } } - return array( $outputFilename, - $prettyDate, - $websiteName, - $reportFormat, - $additionalFiles, + return array( + $outputFilename, + $prettyDate, + $websiteName, + $additionalFiles, ); break; @@ -470,212 +488,149 @@ class Piwik_PDFReports_API } } - public function sendEmailReport($idReport, $idSite, $period = false, $date = false) + public function sendReport($idReport, $period = false, $date = false) { Piwik::checkUserIsNotAnonymous(); - $reports = $this->getReports($idSite, false, $idReport); + + $reports = $this->getReports($idSite = false, false, $idReport); $report = reset($reports); + if($report['period'] == 'never') { $report['period'] = 'day'; } + if(!empty($period)) { $report['period'] = $period; } + if(empty($date)) { $date = Piwik_Date::now()->subPeriod(1, $report['period'])->toString(); } - - // Get user emails and languages - $emails = self::getEmailsFromString($report['additional_emails']); - if($report['email_me'] == 1) - { - if(Piwik::getCurrentUserLogin() == $report['login']) - { - $emails[] = Piwik::getCurrentUserEmail(); - } - elseif($report['login'] == Piwik_Config::getInstance()->superuser['login']) - { - $emails[] = Piwik::getSuperUserEmail(); - } - else - { - try { - $user = Piwik_UsersManager_API::getInstance()->getUser($report['login']); - } catch(Exception $e) { - return; - } - $emails[] = $user['email']; - } - } $language = Piwik_LanguagesManager_API::getInstance()->getLanguageForUser($report['login']); - list($outputFilename, $prettyDate, $websiteName, $reportFormat, $additionalFiles) = + + // generate report + list($outputFilename, $prettyDate, $websiteName, $additionalFiles) = $this->generateReport( $idReport, $date, - $idSite, $language, self::OUTPUT_SAVE_ON_DISK, $report['period'] ); - $this->sendReportEmail($emails, $outputFilename, $prettyDate, $websiteName, $report, $reportFormat, $additionalFiles); - } - - protected function sendReportEmail($emails, $outputFilename, $prettyDate, $websiteName, $report, $reportFormat, $additionalFiles) - { - $periods = self::getPeriodToFrequency(); - $message = Piwik_Translate('PDFReports_EmailHello'); - $subject = Piwik_Translate('General_Report') . ' '. $websiteName . " - ".$prettyDate; - if(!file_exists($outputFilename)) { throw new Exception("The report file wasn't found in $outputFilename"); } + $filename = basename($outputFilename); $handle = fopen($outputFilename, "r"); $contents = fread($handle, filesize($outputFilename)); fclose($handle); - $mail = new Piwik_Mail(); - $mail->setSubject($subject); - $fromEmailName = Piwik_Config::getInstance()->branding['use_custom_logo'] - ? Piwik_Translate('CoreHome_WebAnalyticsReports') - : Piwik_Translate('PDFReports_PiwikReports'); - $fromEmailAddress = Piwik_Config::getInstance()->General['noreply_email_address']; - $attachmentName = $subject; - $mail->setFrom($fromEmailAddress, $fromEmailName); - - switch ($reportFormat) - { - case 'html': - - // Needed when using images as attachment with cid - $mail->setType(Zend_Mime::MULTIPART_RELATED); - $message .= "<br/>" . Piwik_Translate('PDFReports_PleaseFindBelow', array($periods[$report['period']], $websiteName)); - $mail->setBodyHtml($message . "<br/><br/>". $contents); - break; - - default: - case 'pdf': - $message .= "\n" . Piwik_Translate('PDFReports_PleaseFindAttachedFile', array($periods[$report['period']], $websiteName)); - $mail->setBodyText($message); - $mail->createAttachment( $contents, - 'application/pdf', - Zend_Mime::DISPOSITION_INLINE, - Zend_Mime::ENCODING_BASE64, - $attachmentName.'.pdf' - ); - break; - } - - foreach($additionalFiles as $additionalFile) - { - $fileContent = $additionalFile['content']; - $at = $mail->createAttachment( - $fileContent, - $additionalFile['mimeType'], - Zend_Mime::DISPOSITION_INLINE, - $additionalFile['encoding'], - $additionalFile['filename'] - ); - $at->id = $additionalFile['cid']; - - unset($fileContent); - } - - foreach ($emails as $email) - { - $mail->addTo($email); - - try { - $mail->send(); - } catch(Exception $e) { + $notificationObject = null; + Piwik_PostEvent( + self::SEND_REPORT_EVENT, + $notificationObject, + $notificationInfo = array( + self::REPORT_TYPE_INFO_KEY => $report['type'], + self::REPORT_KEY => $report, + self::REPORT_CONTENT_KEY => $contents, + self::FILENAME_KEY => $filename, + self::PRETTY_DATE_KEY => $prettyDate, + self::WEBSITE_NAME_KEY => $websiteName, + self::ADDITIONAL_FILES_KEY => $additionalFiles, + ) + ); - // If running from piwik.php with debug, we ignore the 'email not sent' error - if(!isset($GLOBALS['PIWIK_TRACKER_DEBUG']) || !$GLOBALS['PIWIK_TRACKER_DEBUG']) - { - throw new Exception("An error occured while sending '$filename' ". - " to ". implode(', ',$mail->getRecipients()). - ". Error was '". $e->getMessage()."'"); - } - } - $mail->clearRecipients(); - } // Update flag in DB - Zend_Registry::get('db')->update( Piwik_Common::prefixTable('pdf'), - array( 'ts_last_sent' => Piwik_Date::now()->getDatetime() ), - "idreport = " . $report['idreport'] + Zend_Registry::get('db')->update( Piwik_Common::prefixTable('report'), + array( 'ts_last_sent' => Piwik_Date::now()->getDatetime() ), + "idreport = " . $report['idreport'] ); - // If running from piwik.php with debug, do not delete the PDF after sending the email + // If running from piwik.php with debug, do not delete the PDF after sending the email if(!isset($GLOBALS['PIWIK_TRACKER_DEBUG']) || !$GLOBALS['PIWIK_TRACKER_DEBUG']) { @chmod($outputFilename, 0600); @unlink($outputFilename); } } - - private function checkAdditionalEmails($additionalEmails) + + private static function validateReportParameters($reportType, $parameters) { - if(empty($additionalEmails)) - { - return ''; - } - $additionalEmails = self::getEmailsFromString($additionalEmails); - foreach($additionalEmails as &$email) + // get list of valid parameters + $availableParameters = array(); + + $notificationInfo = array( + self::REPORT_TYPE_INFO_KEY => $reportType + ); + + Piwik_PostEvent(self::GET_REPORT_PARAMETERS_EVENT, $availableParameters, $notificationInfo); + + // unset invalid parameters + $availableParameterKeys = array_keys($availableParameters); + foreach ($parameters as $key => $value) { - $email = trim($email); - if(!Piwik::isValidEmailString($email)) + if(!in_array($key, $availableParameterKeys)) { - throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidEmail') . ' ('.$email.')'); + unset($parameters[$key]); } } - $additionalEmails = implode(',',$additionalEmails); - return $additionalEmails; - } - static protected function getEmailsFromString($additionalEmails) - { - if(empty($additionalEmails)) + // test that all required parameters are provided + foreach($availableParameters as $parameter => $mandatory) { - return array(); + if($mandatory && !isset($parameters[$parameter])) + { + throw new Exception('Missing parameter : ' . $parameter); + } } - $additionalEmails = explode(',', trim($additionalEmails)); - $additionalEmails = array_filter($additionalEmails, 'strlen'); - return $additionalEmails; + + // delegate report parameter validation + Piwik_PostEvent(self::VALIDATE_PARAMETERS_EVENT, $parameters, $notificationInfo); + + return json_encode($parameters); } - - private function checkDescription($description) + + private static function validateDescription($description) { return substr($description, 0, 250); } - - private function checkAvailableReports($idSite, $reports) + + private static function validateRequestedReports($idSite, $reportType, $requestedReports) { - $availableReports = Piwik_API_API::getInstance()->getReportMetadata($idSite); + if(!self::allowMultipleReports($reportType)) + { + //@review sms can only contain one report, we currently silently discard all reports except the first one, is this ok or should we raise an exception? + $requestedReports = array_slice($requestedReports, 0, 1); + } + + // retrieve available reports + $availableReportMetadata = self::getReportMetadata($idSite, $reportType); + $availableReportIds = array(); - foreach($availableReports as $report) + foreach($availableReportMetadata as $reportMetadata) { - $availableReportIds[] = $report['uniqueId']; + $availableReportIds[] = $reportMetadata['uniqueId']; } - $reports = explode(',', $reports); - $reports = array_filter($reports, 'strlen'); - foreach($reports as $report) + + foreach($requestedReports as $report) { if(!in_array($report, $availableReportIds)) { - throw new Exception("Report $report is unknown."); + throw new Exception("Report $report is unknown or not available for report type '$reportType'."); } } - $reports = implode(',', $reports); - return $reports; + + return json_encode($requestedReports); } - - private function checkPeriod($period) + + private static function validateReportPeriod($period) { $availablePeriods = array('day', 'week', 'month', 'never'); if(!in_array($period, $availablePeriods)) @@ -684,30 +639,28 @@ class Piwik_PDFReports_API } } - private function checkFormat($format) + private static function validateReportType($reportType) { - $availableReportRenderers = array_keys(Piwik_ReportRenderer::$availableReportRenderers); - if(!in_array($format, $availableReportRenderers)) + $reportTypes = array_keys(self::getReportTypes()); + + if(!in_array($reportType, $reportTypes)) { throw new Exception( - Piwik_TranslateException( - 'General_ExceptionInvalidReportRendererFormat', - array($format, implode(', ', $availableReportRenderers)) - ) + 'Rerport type \'' . $reportType . '\' not valid. Try one of the following ' . implode(', ', $reportTypes) ); } } - private function checkDisplayFormat($format) + private static function validateReportFormat($reportType, $reportFormat) { - $availableDisplayFormats = array_keys(Piwik_PDFReports_API::getDisplayFormats()); - if(!in_array($format, $availableDisplayFormats)) + $reportFormats = array_keys(self::getReportFormats($reportType)); + + if(!in_array($reportFormat, $reportFormats)) { throw new Exception( Piwik_TranslateException( - // General_ExceptionInvalidAggregateReportsFormat should be named General_ExceptionInvalidDisplayFormat - 'General_ExceptionInvalidAggregateReportsFormat', - array($format, implode(', ', $availableDisplayFormats)) + 'General_ExceptionInvalidReportRendererFormat', + array($reportFormat, implode(', ', $reportFormats)) ) ); } @@ -716,31 +669,83 @@ class Piwik_PDFReports_API /** * @ignore */ - static public function getPeriodToFrequency() + static public function getReportMetadata($idSite, $reportType) + { + $notificationInfo = array( + self::REPORT_TYPE_INFO_KEY => $reportType, + self::ID_SITE_INFO_KEY => $idSite, + ); + + // retrieve available reports + $availableReportMetadata = array(); + Piwik_PostEvent( + self::GET_REPORT_METADATA_EVENT, + $availableReportMetadata, + $notificationInfo + ); + + return $availableReportMetadata; + } + + /** + * @ignore + */ + static public function allowMultipleReports($reportType) + { + $allowMultipleReports = null; + Piwik_PostEvent( + self::ALLOW_MULTIPLE_REPORTS_EVENT, + $allowMultipleReports, + $notificationInfo = array( + self::REPORT_TYPE_INFO_KEY => $reportType, + ) + ); + return $allowMultipleReports; + } + + /** + * @ignore + */ + static public function getReportTypes() + { + $reportTypes = array(); + Piwik_PostEvent(self::GET_REPORT_TYPES_EVENT, $reportTypes); + + return $reportTypes; + } + + /** + * @ignore + */ + static public function getReportFormats($reportType) { - $periods = array( - 'day' => Piwik_Translate('General_Daily'), - 'week' => Piwik_Translate('General_Weekly'), - 'month' => Piwik_Translate('General_Monthly'), - 'range' => Piwik_Translate('General_DateRangeInPeriodList'), + $reportFormats = array(); + + Piwik_PostEvent( + self::GET_REPORT_FORMATS_EVENT, + $reportFormats, + $notificationInfo = array( + self::REPORT_TYPE_INFO_KEY => $reportType + ) ); - return $periods; + + return $reportFormats; } /** * @ignore */ - static public function getDisplayFormats() + static public function getReportRecipients($report) { - $periods = array( - // PDFReports_AggregateReportsFormat_TablesOnly should be named PDFReports_DisplayFormat_GraphsOnlyForKeyMetrics - Piwik_PDFReports::DISPLAY_FORMAT_GRAPHS_ONLY_FOR_KEY_METRICS => Piwik_Translate('PDFReports_AggregateReportsFormat_TablesOnly'), - // PDFReports_AggregateReportsFormat_GraphsOnly should be named PDFReports_DisplayFormat_GraphsOnly - Piwik_PDFReports::DISPLAY_FORMAT_GRAPHS_ONLY => Piwik_Translate('PDFReports_AggregateReportsFormat_GraphsOnly'), - // PDFReports_AggregateReportsFormat_TablesAndGraphs should be named PDFReports_DisplayFormat_TablesAndGraphs - Piwik_PDFReports::DISPLAY_FORMAT_TABLES_AND_GRAPHS => Piwik_Translate('PDFReports_AggregateReportsFormat_TablesAndGraphs'), - Piwik_PDFReports::DISPLAY_FORMAT_TABLES_ONLY => Piwik_Translate('PDFReports_DisplayFormat_TablesOnly'), + $notificationInfo = array( + self::REPORT_TYPE_INFO_KEY => $report['type'], + self::REPORT_KEY => $report, ); - return $periods; + + // retrieve report renderer instance + $recipients = array(); + Piwik_PostEvent(self::GET_REPORT_RECIPIENTS_EVENT, $recipients, $notificationInfo); + + return $recipients; } } diff --git a/plugins/PDFReports/Controller.php b/plugins/PDFReports/Controller.php index 52981d0c5f..36e4f80cf4 100644 --- a/plugins/PDFReports/Controller.php +++ b/plugins/PDFReports/Controller.php @@ -15,51 +15,71 @@ * @package Piwik_PDFReports */ class Piwik_PDFReports_Controller extends Piwik_Controller -{ +{ + const DEFAULT_REPORT_TYPE = Piwik_PDFReports::EMAIL_TYPE; + public function index() { $view = Piwik_View::factory('index'); $this->setGeneralVariablesView($view); - $view->currentUserEmail = Piwik::getCurrentUserEmail(); - $allSites = Piwik_SitesManager_API::getInstance()->getSitesIdWithAtLeastViewAccess(); - $view->countWebsites = count($allSites); - $availableReports = Piwik_API_API::getInstance()->getReportMetadata($this->idSite); - $reportsByCategory = array(); - foreach($availableReports as $report) + $view->countWebsites = count(Piwik_SitesManager_API::getInstance()->getSitesIdWithAtLeastViewAccess()); + + // get report types + $reportTypes = Piwik_PDFReports_API::getReportTypes(); + $view->reportTypes = $reportTypes; + $view->defaultReportType = self::DEFAULT_REPORT_TYPE; + $view->defaultReportFormat = Piwik_PDFReports::DEFAULT_REPORT_FORMAT; + + $reportsByCategoryByType = array(); + $reportFormatsByReportType = array(); + $allowMultipleReportsByReportType = array(); + foreach($reportTypes as $reportType => $reportTypeIcon) { - $reportsByCategory[$report['category']][] = $report; + // get report formats + $reportFormatsByReportType[$reportType] = Piwik_PDFReports_API::getReportFormats($reportType); + $allowMultipleReportsByReportType[$reportType] = Piwik_PDFReports_API::allowMultipleReports($reportType); + + // get report metadata + $reportsByCategory = array(); + $availableReportMetadata = Piwik_PDFReports_API::getReportMetadata($this->idSite, $reportType); + foreach($availableReportMetadata as $reportMetadata) + { + $reportsByCategory[$reportMetadata['category']][] = $reportMetadata; + } + $reportsByCategoryByType[$reportType] = $reportsByCategory; } - unset($reportsByCategory['API']); + $view->reportsByCategoryByReportType = $reportsByCategoryByType; + $view->reportFormatsByReportType = $reportFormatsByReportType; + $view->allowMultipleReportsByReportType = $allowMultipleReportsByReportType; - $reports = $reportsById = array(); + $reports = array(); + $reportsById = array(); if(!Piwik::isUserIsAnonymous()) { $reports = Piwik_PDFReports_API::getInstance()->getReports($this->idSite, $period = false, $idReport = false, $ifSuperUserReturnOnlySuperUserReports = true); - $reportsById = array(); foreach($reports as &$report) { - $report['additional_emails'] = str_replace(',',"\n", $report['additional_emails']); - $report['reports'] = explode(',', str_replace('.','_',$report['reports'])); + $report['recipients'] = Piwik_PDFReports_API::getReportRecipients($report); $reportsById[$report['idreport']] = $report; } } + $view->reports = $reports; + $view->reportsJSON = Piwik_Common::json_encode($reportsById); $view->downloadOutputType = Piwik_PDFReports_API::OUTPUT_DOWNLOAD; - $columnsCount = 2; - $view->newColumnAfter = ceil(count($reportsByCategory) / $columnsCount); - $view->reportsByCategory = $reportsByCategory; - $view->reportsJSON = Piwik_Common::json_encode($reportsById); - $periods = array_merge(array('never' => Piwik_Translate('General_Never')), - Piwik_PDFReports_API::getPeriodToFrequency()); + + $periods = array_merge( + array('never' => Piwik_Translate('General_Never')), + Piwik_PDFReports::getPeriodToFrequency() + ); // Do not display date range in selector unset($periods['range']); $view->periods = $periods; - $view->defaultFormat = Piwik_PDFReports::DEFAULT_FORMAT; - $view->formats = Piwik_ReportRenderer::$availableReportRenderers; - $view->displayFormats = Piwik_PDFReports_API::getDisplayFormats(); - $view->reports = $reports; + $view->defaultPeriod = Piwik_PDFReports::DEFAULT_PERIOD; + $view->language = Piwik_LanguagesManager::getLanguageCodeForCurrentUser(); + echo $view->render(); } } diff --git a/plugins/PDFReports/PDFReports.php b/plugins/PDFReports/PDFReports.php index aea8b708ff..632cac4405 100644 --- a/plugins/PDFReports/PDFReports.php +++ b/plugins/PDFReports/PDFReports.php @@ -16,13 +16,36 @@ */ class Piwik_PDFReports extends Piwik_Plugin { - const DEFAULT_FORMAT = 'pdf'; - const DISPLAY_FORMAT_GRAPHS_ONLY_FOR_KEY_METRICS = 1; // Display Tables Only (Graphs only for key metrics) const DISPLAY_FORMAT_GRAPHS_ONLY = 2; // Display Graphs Only for all reports const DISPLAY_FORMAT_TABLES_AND_GRAPHS = 3; // Display Tables and Graphs for all reports const DISPLAY_FORMAT_TABLES_ONLY = 4; // Display only tables for all reports - const DEFAULT_DISPLAY_FORMAT = Piwik_PDFReports::DISPLAY_FORMAT_GRAPHS_ONLY_FOR_KEY_METRICS; + const DEFAULT_DISPLAY_FORMAT = self::DISPLAY_FORMAT_GRAPHS_ONLY_FOR_KEY_METRICS; + + const DEFAULT_REPORT_FORMAT = Piwik_ReportRenderer::HTML_FORMAT; + const DEFAULT_PERIOD = 'week'; + + const EMAIL_ME_PARAMETER = 'emailMe'; + const ADDITIONAL_EMAILS_PARAMETER = 'additionalEmails'; + const DISPLAY_FORMAT_PARAMETER = 'displayFormat'; + const EMAIL_ME_PARAMETER_DEFAULT_VALUE = true; + + const EMAIL_TYPE = 'email'; + + static private $availableParameters = array( + self::EMAIL_ME_PARAMETER => false, + self::ADDITIONAL_EMAILS_PARAMETER => false, + self::DISPLAY_FORMAT_PARAMETER => true, + ); + + static private $managedReportTypes = array( + self::EMAIL_TYPE => 'themes/default/images/email.png' + ); + + static private $managedReportFormats = array( + Piwik_ReportRenderer::HTML_FORMAT => 'themes/default/images/html_icon.png', + Piwik_ReportRenderer::PDF_FORMAT => 'plugins/UserSettings/images/plugins/pdf.gif' + ); public function getInformation() { @@ -40,6 +63,17 @@ class Piwik_PDFReports extends Piwik_Plugin 'TopMenu.add' => 'addTopMenu', 'TaskScheduler.getScheduledTasks' => 'getScheduledTasks', 'AssetManager.getJsFiles' => 'getJsFiles', + 'PDFReports.getReportParameters' => 'getReportParameters', + 'PDFReports.validateReportParameters' => 'validateReportParameters', + 'PDFReports.getReportMetadata' => 'getReportMetadata', + 'PDFReports.getReportTypes' => 'getReportTypes', + 'PDFReports.getReportFormats' => 'getReportFormats', + 'PDFReports.getRendererInstance' => 'getRendererInstance', + 'PDFReports.getReportRecipients' => 'getReportRecipients', + 'PDFReports.processReports' => 'processReports', + 'PDFReports.allowMultipleReports' => 'allowMultipleReports', + 'PDFReports.sendReport' => 'sendReport', + 'template_reportParametersPDFReports' => 'template_reportParametersPDFReports', 'UsersManager.deleteUser' => 'deleteUserReport', 'SitesManager.deleteSite' => 'deleteSiteReport', ); @@ -48,7 +82,7 @@ class Piwik_PDFReports extends Piwik_Plugin /** * Delete reports for the website * - * @param Piwik_Event_Notification $notification notification object + * @param Piwik_Event_Notification $notification notification object */ function deleteSiteReport( $notification ) { @@ -64,7 +98,7 @@ class Piwik_PDFReports extends Piwik_Plugin } /** - * @param Piwik_Event_Notification $notification notification object + * @param Piwik_Event_Notification $notification notification object */ function getJsFiles( $notification ) { @@ -73,7 +107,360 @@ class Piwik_PDFReports extends Piwik_Plugin } /** - * @param Piwik_Event_Notification $notification notification object + * @param Piwik_Event_Notification $notification notification object + */ + function validateReportParameters( $notification ) + { + if(self::manageEvent($notification)) + { + $parameters = &$notification->getNotificationObject(); + + $reportFormat = $parameters[self::DISPLAY_FORMAT_PARAMETER]; + $availableDisplayFormats = array_keys(self::getDisplayFormats()); + if(!in_array($reportFormat, $availableDisplayFormats)) + { + throw new Exception( + Piwik_TranslateException( + // General_ExceptionInvalidAggregateReportsFormat should be named General_ExceptionInvalidDisplayFormat + 'General_ExceptionInvalidAggregateReportsFormat', + array($reportFormat, implode(', ', $availableDisplayFormats)) + ) + ); + } + + // emailMe is an optional parameter + if(!isset($parameters[self::EMAIL_ME_PARAMETER])) + { + $parameters[self::EMAIL_ME_PARAMETER] = self::EMAIL_ME_PARAMETER_DEFAULT_VALUE; + } + else + { + $parameters[self::EMAIL_ME_PARAMETER] = + filter_var( + $parameters[self::EMAIL_ME_PARAMETER], + FILTER_VALIDATE_BOOLEAN + ); + } + + // additionalEmails is an optional parameter + if(isset($parameters[self::ADDITIONAL_EMAILS_PARAMETER])) + { + $parameters[self::ADDITIONAL_EMAILS_PARAMETER] = self::checkAdditionalEmails($parameters[self::ADDITIONAL_EMAILS_PARAMETER]); + } + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportMetadata( $notification ) + { + if(self::manageEvent($notification)) + { + $reportMetadata = &$notification->getNotificationObject(); + + $notificationInfo = $notification->getNotificationInfo(); + $idSite = $notificationInfo[Piwik_PDFReports_API::ID_SITE_INFO_KEY]; + + $availableReportMetadata = Piwik_API_API::getInstance()->getReportMetadata($idSite); + + $filteredReportMetadata = array(); + foreach($availableReportMetadata as $reportMetadata) + { + // removing reports from the API category and MultiSites.getOne + if( + $reportMetadata['category'] == 'API' || + $reportMetadata['category'] == Piwik_Translate('General_MultiSitesSummary') && $reportMetadata['name'] == Piwik_Translate('General_SingleWebsitesDashboard') + ) continue; + + $filteredReportMetadata[] = $reportMetadata; + } + + $reportMetadata = $filteredReportMetadata; + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportTypes( $notification ) + { + $reportTypes = &$notification->getNotificationObject(); + $reportTypes = array_merge($reportTypes, self::$managedReportTypes); + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportFormats( $notification ) + { + if(self::manageEvent($notification)) + { + $reportFormats = &$notification->getNotificationObject(); + $reportFormats = self::$managedReportFormats; + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportParameters( $notification ) + { + if(self::manageEvent($notification)) + { + $availableParameters = &$notification->getNotificationObject(); + $availableParameters = self::$availableParameters; + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function processReports( $notification ) + { + if(self::manageEvent($notification)) + { + $processedReports = &$notification->getNotificationObject(); + + $notificationInfo = $notification->getNotificationInfo(); + $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]; + + $displayFormat = $report['parameters'][self::DISPLAY_FORMAT_PARAMETER]; + + foreach ($processedReports as &$processedReport) + { + $metadata = $processedReport['metadata']; + + $isAggregateReport = !empty($metadata['dimension']); + + $processedReport['displayTable'] = $displayFormat != self::DISPLAY_FORMAT_GRAPHS_ONLY; + + $processedReport['displayGraph'] = + ($isAggregateReport ? + $displayFormat == self::DISPLAY_FORMAT_GRAPHS_ONLY || $displayFormat == self::DISPLAY_FORMAT_TABLES_AND_GRAPHS + : + $displayFormat != self::DISPLAY_FORMAT_TABLES_ONLY) + && Piwik::isGdExtensionEnabled() + && Piwik_PluginsManager::getInstance()->isPluginActivated('ImageGraph') + && !empty($metadata['imageGraphUrl']); + + // remove evolution metrics from MultiSites.getAll + if($metadata['module'] == 'MultiSites') + { + $columns = $processedReport['columns']; + + foreach(Piwik_MultiSites_API::getApiMetrics($enhanced = true) as $metricSettings) + { + unset($columns[$metricSettings[Piwik_MultiSites_API::METRIC_EVOLUTION_COL_NAME_KEY]]); + } + + $processedReport['metadata'] = $metadata; + $processedReport['columns'] = $columns; + } + } + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getRendererInstance( $notification ) + { + if(self::manageEvent($notification)) + { + $reportRenderer = &$notification->getNotificationObject(); + $notificationInfo = $notification->getNotificationInfo(); + + $reportFormat = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]['format']; + + $reportRenderer = Piwik_ReportRenderer::factory($reportFormat); + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function allowMultipleReports( $notification ) + { + if(self::manageEvent($notification)) + { + $allowMultipleReports = &$notification->getNotificationObject(); + $allowMultipleReports = true; + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function sendReport( $notification ) + { + if(self::manageEvent($notification)) + { + $notificationInfo = $notification->getNotificationInfo(); + $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]; + $websiteName = $notificationInfo[Piwik_PDFReports_API::WEBSITE_NAME_KEY]; + $prettyDate = $notificationInfo[Piwik_PDFReports_API::PRETTY_DATE_KEY]; + $contents = $notificationInfo[Piwik_PDFReports_API::REPORT_CONTENT_KEY]; + $filename = $notificationInfo[Piwik_PDFReports_API::FILENAME_KEY]; + $additionalFiles = $notificationInfo[Piwik_PDFReports_API::ADDITIONAL_FILES_KEY]; + + $periods = self::getPeriodToFrequency(); + $message = Piwik_Translate('PDFReports_EmailHello'); + $subject = Piwik_Translate('General_Report') . ' '. $websiteName . " - ".$prettyDate; + + $mail = new Piwik_Mail(); + $mail->setSubject($subject); + $fromEmailName = Piwik_Config::getInstance()->branding['use_custom_logo'] + ? Piwik_Translate('CoreHome_WebAnalyticsReports') + : Piwik_Translate('PDFReports_PiwikReports'); + $fromEmailAddress = Piwik_Config::getInstance()->General['noreply_email_address']; + $attachmentName = $subject; + $mail->setFrom($fromEmailAddress, $fromEmailName); + + switch ($report['format']) + { + case 'html': + + // Needed when using images as attachment with cid + $mail->setType(Zend_Mime::MULTIPART_RELATED); + $message .= "<br/>" . Piwik_Translate('PDFReports_PleaseFindBelow', array($periods[$report['period']], $websiteName)); + $mail->setBodyHtml($message . "<br/><br/>". $contents); + break; + + default: + case 'pdf': + $message .= "\n" . Piwik_Translate('PDFReports_PleaseFindAttachedFile', array($periods[$report['period']], $websiteName)); + $mail->setBodyText($message); + $mail->createAttachment( + $contents, + 'application/pdf', + Zend_Mime::DISPOSITION_INLINE, + Zend_Mime::ENCODING_BASE64, + $attachmentName.'.pdf' + ); + break; + } + + foreach($additionalFiles as $additionalFile) + { + $fileContent = $additionalFile['content']; + $at = $mail->createAttachment( + $fileContent, + $additionalFile['mimeType'], + Zend_Mime::DISPOSITION_INLINE, + $additionalFile['encoding'], + $additionalFile['filename'] + ); + $at->id = $additionalFile['cid']; + + unset($fileContent); + } + + // Get user emails and languages + $reportParameters = $report['parameters']; + $emails = array(); + + if(isset($reportParameters[self::ADDITIONAL_EMAILS_PARAMETER])) + { + $emails = $reportParameters[self::ADDITIONAL_EMAILS_PARAMETER]; + } + + if($reportParameters[self::EMAIL_ME_PARAMETER] == 1) + { + if(Piwik::getCurrentUserLogin() == $report['login']) + { + $emails[] = Piwik::getCurrentUserEmail(); + } + elseif($report['login'] == Piwik_Config::getInstance()->superuser['login']) + { + $emails[] = Piwik::getSuperUserEmail(); + } + else + { + try { + $user = Piwik_UsersManager_API::getInstance()->getUser($report['login']); + } catch(Exception $e) { + return; + } + $emails[] = $user['email']; + } + } + + foreach ($emails as $email) + { + $mail->addTo($email); + + try { + $mail->send(); + } catch(Exception $e) { + + // If running from piwik.php with debug, we ignore the 'email not sent' error + if(!isset($GLOBALS['PIWIK_TRACKER_DEBUG']) || !$GLOBALS['PIWIK_TRACKER_DEBUG']) + { + throw new Exception("An error occured while sending '$filename' ". + " to ". implode(', ',$mail->getRecipients()). + ". Error was '". $e->getMessage()."'"); + } + } + $mail->clearRecipients(); + } + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + function getReportRecipients( $notification ) + { + if(self::manageEvent($notification)) + { + $recipients = &$notification->getNotificationObject(); + $notificationInfo = $notification->getNotificationInfo(); + + $report = $notificationInfo[Piwik_PDFReports_API::REPORT_KEY]; + $parameters = $report['parameters']; + $eMailMe = $parameters[self::EMAIL_ME_PARAMETER]; + + if($eMailMe) + { + $recipients[] = Piwik::getCurrentUserEmail(); + } + + if(isset($parameters[self::ADDITIONAL_EMAILS_PARAMETER])) + { + $additionalEMails = $parameters[self::ADDITIONAL_EMAILS_PARAMETER]; + $recipients = array_merge($recipients, $additionalEMails); + } + } + } + + /** + * @param Piwik_Event_Notification $notification notification object + */ + static public function template_reportParametersPDFReports($notification) + { + $out =& $notification->getNotificationObject(); + + $view = Piwik_View::factory('report_parameters'); + $view->currentUserEmail = Piwik::getCurrentUserEmail(); + $view->displayFormats = self::getDisplayFormats(); + $view->reportType = self::EMAIL_TYPE; + $view->defaultDisplayFormat = self::DEFAULT_DISPLAY_FORMAT; + $view->defaultEmailMe = self::EMAIL_ME_PARAMETER_DEFAULT_VALUE; + $out .= $view->render(); + } + + private static function manageEvent($notification) + { + $notificationInfo = $notification->getNotificationInfo(); + return in_array( + $notificationInfo[Piwik_PDFReports_API::REPORT_TYPE_INFO_KEY], + array_keys(self::$managedReportTypes) + ); + } + + /** + * @param Piwik_Event_Notification $notification notification object */ function getScheduledTasks ( $notification ) { @@ -136,44 +523,47 @@ class Piwik_PDFReports extends Piwik_Plugin // For each, generate the file and send the message with the attached report foreach($reportsToGenerate as $report) { - Piwik_PDFReports_API::getInstance()->sendEmailReport( $report['idreport'], - $report['idsite']); + Piwik_PDFReports_API::getInstance()->sendReport($report['idreport']); } } function addTopMenu() { - Piwik_AddTopMenu( 'PDFReports_EmailReports', array('module' => 'PDFReports', 'action' => 'index'), true, 13); + Piwik_AddTopMenu( + Piwik_PluginsManager::getInstance()->isPluginActivated('MobileMessaging') ? 'MobileMessaging_TopMenu' : 'PDFReports_EmailReports', + array('module' => 'PDFReports', 'action' => 'index'), + true, + 13 + ); } /** - * @param Piwik_Event_Notification $notification notification object + * @param Piwik_Event_Notification $notification notification object */ function deleteUserReport($notification) { $userLogin = $notification->getNotificationObject(); - Piwik_Query('DELETE FROM ' . Piwik_Common::prefixTable('pdf') . ' WHERE login = ?', $userLogin); + Piwik_Query('DELETE FROM ' . Piwik_Common::prefixTable('report') . ' WHERE login = ?', $userLogin); } function install() { - $queries[] = " - CREATE TABLE ".Piwik_Common::prefixTable('pdf')." ( - idreport INT(11) NOT NULL AUTO_INCREMENT, - idsite INTEGER(11) NOT NULL, - login VARCHAR(100) NOT NULL, - description VARCHAR(255) NOT NULL, - period VARCHAR(10) NULL, - format VARCHAR(10), - display_format TINYINT(1) NOT NULL, - email_me TINYINT NULL, - additional_emails TEXT NULL, - reports TEXT NOT NULL, - ts_created TIMESTAMP NULL, - ts_last_sent TIMESTAMP NULL, - deleted tinyint(4) NOT NULL default '0', - PRIMARY KEY (idreport) - ) DEFAULT CHARSET=utf8"; + $queries[] = ' + CREATE TABLE `'.Piwik_Common::prefixTable('report').'` ( + `idreport` INT(11) NOT NULL AUTO_INCREMENT, + `idsite` INTEGER(11) NOT NULL, + `login` VARCHAR(100) NOT NULL, + `description` VARCHAR(255) NOT NULL, + `period` VARCHAR(10) NOT NULL, + `type` VARCHAR(10) NOT NULL, + `format` VARCHAR(10) NOT NULL, + `reports` TEXT NOT NULL, + `parameters` TEXT NULL, + `ts_created` TIMESTAMP NULL, + `ts_last_sent` TIMESTAMP NULL, + `deleted` tinyint(4) NOT NULL default 0, + PRIMARY KEY (`idreport`) + ) DEFAULT CHARSET=utf8'; try { foreach($queries as $query) { @@ -187,4 +577,45 @@ class Piwik_PDFReports extends Piwik_Plugin } } } + + private static function checkAdditionalEmails($additionalEmails) + { + foreach($additionalEmails as &$email) + { + $email = trim($email); + if(!Piwik::isValidEmailString($email)) + { + throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidEmail') . ' ('.$email.')'); + } + } + return $additionalEmails; + } + + private static function getDisplayFormats() + { + $displayFormats = array( + // PDFReports_AggregateReportsFormat_TablesOnly should be named PDFReports_DisplayFormat_GraphsOnlyForKeyMetrics + self::DISPLAY_FORMAT_GRAPHS_ONLY_FOR_KEY_METRICS => Piwik_Translate('PDFReports_AggregateReportsFormat_TablesOnly'), + // PDFReports_AggregateReportsFormat_GraphsOnly should be named PDFReports_DisplayFormat_GraphsOnly + self::DISPLAY_FORMAT_GRAPHS_ONLY => Piwik_Translate('PDFReports_AggregateReportsFormat_GraphsOnly'), + // PDFReports_AggregateReportsFormat_TablesAndGraphs should be named PDFReports_DisplayFormat_TablesAndGraphs + self::DISPLAY_FORMAT_TABLES_AND_GRAPHS => Piwik_Translate('PDFReports_AggregateReportsFormat_TablesAndGraphs'), + self::DISPLAY_FORMAT_TABLES_ONLY => Piwik_Translate('PDFReports_DisplayFormat_TablesOnly'), + ); + return $displayFormats; + } + + /** + * @ignore + */ + static public function getPeriodToFrequency() + { + $periods = array( + 'day' => Piwik_Translate('General_Daily'), + 'week' => Piwik_Translate('General_Weekly'), + 'month' => Piwik_Translate('General_Monthly'), + 'range' => Piwik_Translate('General_DateRangeInPeriodList'), + ); + return $periods; + } } diff --git a/plugins/PDFReports/templates/add.tpl b/plugins/PDFReports/templates/add.tpl index 219b6eb80e..d2492aa9eb 100644 --- a/plugins/PDFReports/templates/add.tpl +++ b/plugins/PDFReports/templates/add.tpl @@ -31,7 +31,9 @@ <td> <select id="report_period" class="inp"> {foreach from=$periods item=period key=periodId} - <option value="{$periodId}">{$period}</option> + <option value="{$periodId}"> + {$period} + </option> {/foreach} </select> @@ -42,81 +44,94 @@ </div> </td> </tr> - <tr> - <td style='width:240px;' class="first">{'PDFReports_SendReportTo'|translate} - </td> - <td> - <input type="checkbox" id="report_email_me" /> - <label for="report_email_me">{'PDFReports_SentToMe'|translate} (<i>{$currentUserEmail}</i>) </label> - <br/><br/> - {'PDFReports_AlsoSendReportToTheseEmails'|translate}<br/> - <textarea cols="30" rows="3" id="report_additional_emails" class="inp"></textarea> - </td> - </tr> - <tr> - <td class="first"> - {'PDFReports_ReportFormat'|translate} + + <tr {if $reportTypes|@count eq 1}style='display:none'{/if}> + <td class='first'> + {'PDFReports_ReportType'|translate} </td> <td> - <select id="report_format"> - {foreach from=$formats key=format item=icon} - <option value="{$format}">{$format|upper}</option> + <select id='report_type'> + {foreach from=$reportTypes key=reportType item=reportTypeIcon} + <option value="{$reportType}">{$reportType|upper}</option> {/foreach} </select> </td> </tr> + <tr> - <td class="first"> - {* PDFReports_AggregateReportsFormat should be named PDFReports_DisplayFormat *} - {'PDFReports_AggregateReportsFormat'|translate} + <td class='first'> + {'PDFReports_ReportFormat'|translate} </td> + <td> - <select id="display_format"> - {foreach from=$displayFormats key=formatValue item=formatLabel} - <option {if $formatValue==1}selected{/if} value="{$formatValue}">{$formatLabel}</option> + {foreach from=$reportFormatsByReportType key=reportType item=reportFormats} + <select name='report_format' class='{$reportType}'> + {foreach from=$reportFormats key=reportFormat item=reportFormatIcon} + <option value="{$reportFormat}">{$reportFormat|upper}</option> + {/foreach} + </select> {/foreach} - </select> </td> </tr> + + {postEvent name="template_reportParametersPDFReports"} + <tr> <td class="first">{'PDFReports_ReportsIncluded'|translate}</td> <td> - <div id='reportsList'> - {assign var=countReports value=0} - <div id='leftcolumn'> - {foreach from=$reportsByCategory item=reports key=category name=reports} - {if $countReports >= $newColumnAfter && $newColumnAfter != 0} - {assign var=newColumnAfter value=0} - </div><div id='rightcolumn'> + {foreach from=$reportsByCategoryByReportType key=reportType item=reportsByCategory} + <div name='reportsList' class='{$reportType}'> + + {if $allowMultipleReportsByReportType[$reportType]} + {assign var=reportInputType value='checkbox'} + {else} + {assign var=reportInputType value='radio'} {/if} - <div class='reportCategory'>{$category}</div><ul class='listReports'> - {foreach from=$reports item=report} - <li> - <input type="checkbox" id="{$report.uniqueId}" /> - <label for="{$report.uniqueId}"> - {$report.name|escape:"html"} - {if $report.uniqueId=='MultiSites_getAll'} - <div class="entityInlineHelp">{'PDFReports_ReportIncludeNWebsites'|translate:"$countWebsites "}</div> - {/if} - </label> - </li> + + {assign var=countCategory value=0} + + {math + equation="ceil (reportsByCategoryCount / 2)" + reportsByCategoryCount=$reportsByCategory|@count + assign=newColumnAfter + } + + <div id='leftcolumn'> + {foreach from=$reportsByCategory item=reports key=category name=reports} + {if $countCategory >= $newColumnAfter && $newColumnAfter != 0} + {assign var=newColumnAfter value=0} + </div><div id='rightcolumn'> + {/if} + <div class='reportCategory'>{$category}</div><ul class='listReports'> + {foreach from=$reports item=report} + <li> + <input type='{$reportInputType}' id="{$report.uniqueId}" name='{$reportType}Reports'/> + <label for="{$report.uniqueId}"> + {$report.name|escape:"html"} + {if $report.uniqueId=='MultiSites_getAll'} + <div class="entityInlineHelp">{'PDFReports_ReportIncludeNWebsites'|translate:"$countWebsites "}</div> + {/if} + </label> + </li> + {/foreach} + {assign var=countCategory value=$countCategory+1} + </ul> + <br/> {/foreach} - {assign var=countReports value=$countReports+1} - </ul> - <br/> - {/foreach} + </div> </div> - </div> + {/foreach} </td> </tr> </tbody> </table> -<input type="hidden" id="report_idreport" value=""> -<input type="submit" value="{'PDFReports_CreateReport'|translate}" name="submit" id="report_submit" class="submit" /> + + <input type="hidden" id="report_idreport" value=""> + <input type="submit" id="report_submit" name="submit" class="submit"/> + </form> <div class='entityCancel'> {'General_OrCancel'|translate:"<a class='entityCancelLink'>":"</a>"} </div> -</div> - +</div>
\ No newline at end of file diff --git a/plugins/PDFReports/templates/index.tpl b/plugins/PDFReports/templates/index.tpl index 58a44ad1d1..5b36692ed8 100644 --- a/plugins/PDFReports/templates/index.tpl +++ b/plugins/PDFReports/templates/index.tpl @@ -25,8 +25,13 @@ </div> <script type="text/javascript"> -piwik.PDFReports = {$reportsJSON}; -piwik.updateReportString = "{'PDFReports_UpdateReport'|translate}"; +var ReportPlugin = new Object(); +ReportPlugin.defaultPeriod = '{$defaultPeriod}'; +ReportPlugin.defaultReportType = '{$defaultReportType}'; +ReportPlugin.defaultReportFormat = '{$defaultReportFormat}'; +ReportPlugin.reportList = {$reportsJSON}; +ReportPlugin.createReportString = "{'PDFReports_CreateReport'|translate}"; +ReportPlugin.updateReportString = "{'PDFReports_UpdateReport'|translate}"; {literal} $(document).ready( function() { initManagePdf(); diff --git a/plugins/PDFReports/templates/list.tpl b/plugins/PDFReports/templates/list.tpl index 35b9755853..d21ef65b92 100644 --- a/plugins/PDFReports/templates/list.tpl +++ b/plugins/PDFReports/templates/list.tpl @@ -39,21 +39,45 @@ <td> {if !empty($report.format)} {$report.format|upper} + {/if} + </td> + <td> + {*report recipients*} + {if $report.recipients|@count eq 0} + {'PDFReports_NoRecipients'|translate} {else} - {$defaultFormat} + {foreach name=recipients from=$report.recipients item=recipient} + {$recipient}<br/> + {/foreach} + {*send now link*} + <a href='#' idreport='{$report.idreport}' name='linkSendNow' class="link_but" style='margin-top:3px'> + <img border=0 src='{$reportTypes[$report.type]}'/> + {'PDFReports_SendReportNow'|translate} + </a> {/if} </td> - <td>{if $report.email_me == 1}{$currentUserEmail}{if !empty($report.additional_emails)}<br/>{/if}{/if} - {$report.additional_emails|replace:",":" "} - <br/><a href='#' idreport='{$report.idreport}' name='linkEmailNow' class="link_but" style='margin-top:3px'><img border=0 src='themes/default/images/email.png'/> {'PDFReports_SendReportNow'|translate}</a> - </td> <td> - <a href="{url module=API token_auth=$token_auth method='PDFReports.generateReport' idSite=$idSite date=$rawDate idReport=$report.idreport outputType=$downloadOutputType language=$language reportFormat=$report.format}" + {*download link*} + <a href="{url module=API token_auth=$token_auth method='PDFReports.generateReport' date=$rawDate idReport=$report.idreport outputType=$downloadOutputType language=$language}" target="_blank" name="linkDownloadReport" id="{$report.idreport}" class="link_but"> - <img src='{$formats[$report.format]}' border="0" /> {'General_Download'|translate}</a> + <img src='{$reportFormatsByReportType[$report.type][$report.format]}' border="0" /> + {'General_Download'|translate} + </a> + </td> + <td> + {*edit link*} + <a href='#' name="linkEditReport" id="{$report.idreport}" class="link_but"> + <img src='themes/default/images/ico_edit.png' border="0" /> + {'General_Edit'|translate} + </a> + </td> + <td> + {*delete link *} + <a href='#' name="linkDeleteReport" id="{$report.idreport}" class="link_but"> + <img src='themes/default/images/ico_delete.png' border="0" /> + {'General_Delete'|translate} + </a> </td> - <td><a href='#' name="linkEditReport" id="{$report.idreport}" class="link_but"><img src='themes/default/images/ico_edit.png' border="0" /> {'General_Edit'|translate}</a></td> - <td><a href='#' name="linkDeleteReport" id="{$report.idreport}" class="link_but"><img src='themes/default/images/ico_delete.png' border="0" /> {'General_Delete'|translate}</a></td> </tr> {/foreach} </table> diff --git a/plugins/PDFReports/templates/pdf.js b/plugins/PDFReports/templates/pdf.js index 100553d9cf..e4c5160a89 100644 --- a/plugins/PDFReports/templates/pdf.js +++ b/plugins/PDFReports/templates/pdf.js @@ -5,41 +5,51 @@ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ +var getReportParametersFunctions = Object(); +var updateReportParametersFunctions = Object(); +var resetReportParametersFunctions = Object(); + function formSetEditReport(idReport) { - var report = { "description":"", - "period":"week", - "email_me":"1", - "additional_emails":"", - "reports":[] + var report = { + 'type' : ReportPlugin.defaultReportType, + 'format' : ReportPlugin.defaultReportFormat, + 'description' : '', + 'period' : ReportPlugin.defaultPeriod, + 'reports' : [] }; - + if(idReport > 0) { - report = piwik.PDFReports[idReport]; + report = ReportPlugin.reportList[idReport]; + $('#report_submit').val(ReportPlugin.updateReportString); } - $('#report_description').html(report.description); - $('#report_period option[value='+report.period+']').prop('selected', 'selected'); - $('#report_format option[value='+report.format+']').prop('selected', 'selected'); - $('#display_format option[value='+report.display_format+']').prop('selected', 'selected'); - if(report.email_me == 1) + else { - $('#report_email_me').prop('checked','checked'); + $('#report_submit').val(ReportPlugin.createReportString); } - $('#report_additional_emails').text(report.additional_emails); - - $('#reportsList input').prop('checked', false); + + toggleReportType(report.type); + + $('#report_description').html(report.description); + $('#report_type option[value='+report.type+']').prop('selected', 'selected'); + $('#report_period option[value='+report.period+']').prop('selected', 'selected'); + $('[name=report_format].'+report.type+' option[value='+report.format+']').prop('selected', 'selected'); + + $('[name=reportsList] input').prop('checked', false); var key; for(key in report.reports) { - $('#'+report.reports[key]).prop('checked','checked'); + $('.' + report.type + ' #' + report.reports[key]).prop('checked','checked'); } + + updateReportParametersFunctions[report.type](report.parameters); + $('#report_idreport').val(idReport); - $('#report_submit').val(piwik.updateReportString); } -function getPDFAjaxRequest(idReport, defaultApiMethod) +function getReportAjaxRequest(idReport, defaultApiMethod) { var parameters = {}; piwikHelper.lazyScrollTo(".entityContainer", 400); @@ -55,39 +65,51 @@ function getPDFAjaxRequest(idReport, defaultApiMethod) return parameters; } +function toggleReportType(reportType) +{ + resetReportParametersFunctions[reportType](); + $('#report_type option').each(function(index, type) { + $('.'+$(type).val()).hide(); + }); + $('.'+reportType).show(); +} + function initManagePdf() { // Click Add/Update Submit $('#addEditReport').submit( function() { - idReport = $('#report_idreport').val(); - parameters = getPDFAjaxRequest(idReport, 'PDFReports.updateReport'); - parameters.idReport = idReport; - parameters.description = $('#report_description').val(); - parameters.period = $('#report_period option:selected').val(); - parameters.reportFormat = $('#report_format option:selected').val(); - parameters.displayFormat = $('#display_format option:selected').val(); - parameters.emailMe = $('#report_email_me').prop('checked') == true ? 1: 0; - additionalEmails = $('#report_additional_emails').val(); - parameters.additionalEmails = piwikHelper.getApiFormatTextarea(additionalEmails); - reports = ''; - $('#reportsList input:checked').each(function() { - reports += $(this).attr('id') + ','; + var idReport = $('#report_idreport').val(); + var apiParameters = getReportAjaxRequest(idReport, 'PDFReports.updateReport'); + apiParameters.idReport = idReport; + apiParameters.description = $('#report_description').val(); + apiParameters.period = $('#report_period option:selected').val(); + apiParameters.reportType = $('#report_type option:selected').val(); + apiParameters.reportFormat = $('[name=report_format].'+apiParameters.reportType+' option:selected').val(); + + var reports = []; + $('[name=reportsList].'+apiParameters.reportType+' input:checked').each(function() { + reports.push($(this).attr('id')); }); - parameters.reports = reports; + if(reports.length > 0) + { + apiParameters.reports = reports; + } + + apiParameters.parameters = getReportParametersFunctions[apiParameters.reportType](); var ajaxRequest = piwikHelper.getStandardAjaxConf(); ajaxRequest.type = 'POST'; - ajaxRequest.data = parameters; + ajaxRequest.data = apiParameters; $.ajax( ajaxRequest ); return false; }); // Email now - $('a[name=linkEmailNow]').click(function(){ + $('a[name=linkSendNow]').click(function(){ var idReport = $(this).attr('idreport'); var ajaxRequest = piwikHelper.getStandardAjaxConf(); ajaxRequest.type = 'POST'; - parameters = getPDFAjaxRequest(idReport, 'PDFReports.sendEmailReport'); + parameters = getReportAjaxRequest(idReport, 'PDFReports.sendReport'); parameters.idReport = idReport; parameters.period = broadcast.getValueFromUrl('period'); parameters.date = broadcast.getValueFromUrl('date'); @@ -95,14 +117,14 @@ function initManagePdf() $.ajax( ajaxRequest ); }); - // Delete PDF + // Delete Report $('a[name=linkDeleteReport]').click(function(){ var idReport = $(this).attr('id'); function onDelete() { var ajaxRequest = piwikHelper.getStandardAjaxConf(); ajaxRequest.type = 'POST'; - parameters = getPDFAjaxRequest(idReport, 'PDFReports.deleteReport'); + parameters = getReportAjaxRequest(idReport, 'PDFReports.deleteReport'); parameters.idReport = idReport; ajaxRequest.data = parameters; $.ajax( ajaxRequest ); @@ -116,8 +138,14 @@ function initManagePdf() formSetEditReport( idReport ); $('.entityAddContainer').show(); $('#entityEditContainer').hide(); - }); - + }); + + // Switch Report Type + $('#report_type').change(function(){ + var reportType = $(this).val(); + toggleReportType(reportType); + }); + // Add a Report click $('#linkAddReport').click(function(){ $('.entityAddContainer').show(); diff --git a/plugins/PDFReports/templates/report_parameters.tpl b/plugins/PDFReports/templates/report_parameters.tpl new file mode 100644 index 0000000000..d009d331bc --- /dev/null +++ b/plugins/PDFReports/templates/report_parameters.tpl @@ -0,0 +1,73 @@ +<script> + $(function() {ldelim} + resetReportParametersFunctions ['{$reportType}'] = + function () {ldelim} + + var reportParameters = {ldelim} + 'displayFormat' : '{$defaultDisplayFormat}', + 'emailMe' : {$defaultEmailMe} == 1, + 'additionalEmails' : null + {rdelim}; + + updateReportParametersFunctions['{$reportType}'](reportParameters); + {rdelim}; + + updateReportParametersFunctions['{$reportType}'] = + function (reportParameters) {ldelim} + + if(reportParameters == null) return; + + $('#display_format option[value='+reportParameters.displayFormat+']').prop('selected', 'selected'); + + if(reportParameters.emailMe === true) + $('#report_email_me').prop('checked', 'checked'); + else + $('#report_email_me').removeProp('checked'); + + if(reportParameters.additionalEmails != null) + $('#report_additional_emails').text(reportParameters.additionalEmails.join('\n')); + else + $('#report_additional_emails').html(''); + {rdelim}; + + getReportParametersFunctions['{$reportType}'] = + function () {ldelim} + + var parameters = Object(); + + parameters.displayFormat = $('#display_format option:selected').val(); + parameters.emailMe = $('#report_email_me').prop('checked'); + + additionalEmails = $('#report_additional_emails').val(); + parameters.additionalEmails = + additionalEmails != '' ? additionalEmails.split('\n') : []; + + return parameters; + {rdelim}; + {rdelim}); +</script> + +<tr class='{$reportType}'> + <td style='width:240px;' class="first">{'PDFReports_SendReportTo'|translate} + </td> + <td> + <input type="checkbox" id="report_email_me"/> + <label for="report_email_me">{'PDFReports_SentToMe'|translate} (<i>{$currentUserEmail}</i>) </label> + <br/><br/> + {'PDFReports_AlsoSendReportToTheseEmails'|translate}<br/> + <textarea cols="30" rows="3" id="report_additional_emails" class="inp"></textarea> + </td> +</tr> +<tr class='{$reportType}'> + <td class="first"> + {*PDFReports_AggregateReportsFormat should be named PDFReports_DisplayFormat*} + {'PDFReports_AggregateReportsFormat'|translate} + </td> + <td> + <select id="display_format"> + {foreach from=$displayFormats key=formatValue item=formatLabel} + <option {if $formatValue==1}selected{/if} value="{$formatValue}">{$formatLabel}</option> + {/foreach} + </select> + </td> +</tr> diff --git a/plugins/PDFReports/tests/PDFReports.test.php b/plugins/PDFReports/tests/PDFReports.test.php index 2c551fbd6c..88b39d0256 100644 --- a/plugins/PDFReports/tests/PDFReports.test.php +++ b/plugins/PDFReports/tests/PDFReports.test.php @@ -40,7 +40,7 @@ class Test_Piwik_PDFReports extends Test_Database function tearDown() { - Piwik_Query('TRUNCATE '.Piwik_Common::prefixTable('pdf')); + Piwik_Query('TRUNCATE '.Piwik_Common::prefixTable('report')); Piwik_PDFReports_API::$cache = array(); } @@ -49,12 +49,15 @@ class Test_Piwik_PDFReports extends Test_Database $data = array( 'idsite' => $this->idSiteAccess, 'description' => 'test description"', + 'type' => 'email', 'period' => 'day', 'format' => 'pdf', - 'display_format' => '1', - 'reports' => 'UserCountry_getCountry', - 'email_me' => 1, - 'additional_emails' => 'test@test.com, t2@test.com', + 'reports' => array('UserCountry_getCountry'), + 'parameters' => array( + 'displayFormat' => '1', + 'emailMe' => true, + 'additionalEmails' => array('test@test.com', 't2@test.com') + ) ); $dataWebsiteTwo = $data; @@ -172,11 +175,14 @@ class Test_Piwik_PDFReports extends Test_Database 'idsite' => $this->idSiteAccess, 'description' => 'test description"', 'period' => 'day', + 'type' => 'email', 'format' => 'pdf', - 'display_format' => '1', - 'reports' => 'UserCountry_getCountry', - 'email_me' => 1, - 'additional_emails' => 'test@test.com, t2@test.com', + 'reports' => array('UserCountry_getCountry'), + 'parameters' => array( + 'displayFormat' => '1', + 'emailMe' => true, + 'additionalEmails' => array('test@test.com', 't2@test.com') + ) ); } @@ -186,11 +192,14 @@ class Test_Piwik_PDFReports extends Test_Database 'idsite' => $this->idSiteAccess, 'description' => 'very very long and possibly truncated description. very very long and possibly truncated description. very very long and possibly truncated description. very very long and possibly truncated description. very very long and possibly truncated description. ', 'period' => 'month', + 'type' => 'email', 'format' => 'pdf', - 'display_format' => '1', - 'reports' => 'UserCountry_getContinent', - 'email_me' => 0, - 'additional_emails' => 'blabla@ec.fr', + 'reports' => array('UserCountry_getContinent'), + 'parameters' => array( + 'displayFormat' => '1', + 'emailMe' => false, + 'additionalEmails' => array('blabla@ec.fr') + ) ); } function _createReport($data) @@ -199,27 +208,25 @@ class Test_Piwik_PDFReports extends Test_Database $data['idsite'], $data['description'], $data['period'], + $data['type'], $data['format'], - $data['display_format'], $data['reports'], - $data['email_me'], - $data['additional_emails']); + $data['parameters'] + ); return $idReport; } function _updateReport($idReport, $data) { - //$idReport, $idSite, $description, $period, $reports, $emailMe = true, $additionalEmails = false) $idReport = Piwik_PDFReports_API::getInstance()->updateReport( $idReport, $data['idsite'], $data['description'], $data['period'], + $data['type'], $data['format'], - $data['display_format'], $data['reports'], - $data['email_me'], - $data['additional_emails']); + $data['parameters']); return $idReport; } @@ -227,7 +234,6 @@ class Test_Piwik_PDFReports extends Test_Database { foreach($data as $key => $value) { - if($key == 'additional_emails') $value = str_replace(' ','', $value); if($key == 'description') $value = substr($value,0,250); $this->assertEqual($value, $report[$key], "Error for $key for report ".var_export($report ,true)." and data ".var_export($data,true)." ---> %s "); } diff --git a/tests/integration/expected/test_OneVisitorTwoVisits__MultiSites.getOne_day.xml b/tests/integration/expected/test_OneVisitorTwoVisits__MultiSites.getOne_day.xml new file mode 100644 index 0000000000..a8e4a6dc7b --- /dev/null +++ b/tests/integration/expected/test_OneVisitorTwoVisits__MultiSites.getOne_day.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result> + <nb_visits>2</nb_visits> + <nb_actions>8</nb_actions> + <revenue>43</revenue> + <visits_evolution>100%</visits_evolution> + <actions_evolution>100%</actions_evolution> + <revenue_evolution>100%</revenue_evolution> +</result> diff --git a/tests/integration/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml b/tests/integration/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml index 702026b582..d00892b104 100644 --- a/tests/integration/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml +++ b/tests/integration/expected/test_TwoVisitors_twoWebsites_differentDays_Conversions_MultiSites.getAll_firstSite_lastN__API.getProcessedReport_day.xml @@ -10,8 +10,17 @@ <dimension>Website</dimension> <metrics> <nb_visits>Visits</nb_visits> + <visits_evolution>Visits Evolution</visits_evolution> <nb_actions>Actions</nb_actions> + <actions_evolution>Actions Evolution</actions_evolution> <revenue>Revenue</revenue> + <revenue_evolution>Revenue Evolution</revenue_evolution> + <nb_conversions>Conversions</nb_conversions> + <nb_conversions_evolution>Conversions Evolution</nb_conversions_evolution> + <orders>Ecommerce Orders</orders> + <orders_evolution>Ecommerce Orders Evolution</orders_evolution> + <ecommerce_revenue>Product Revenue</ecommerce_revenue> + <ecommerce_revenue_evolution>Product Revenue Evolution</ecommerce_revenue_evolution> </metrics> <metricsDocumentation> @@ -34,8 +43,17 @@ <columns> <label>Website</label> <nb_visits>Visits</nb_visits> + <visits_evolution>Visits Evolution</visits_evolution> <nb_actions>Actions</nb_actions> + <actions_evolution>Actions Evolution</actions_evolution> <revenue>Revenue</revenue> + <revenue_evolution>Revenue Evolution</revenue_evolution> + <nb_conversions>Conversions</nb_conversions> + <nb_conversions_evolution>Conversions Evolution</nb_conversions_evolution> + <orders>Ecommerce Orders</orders> + <orders_evolution>Ecommerce Orders Evolution</orders_evolution> + <ecommerce_revenue>Product Revenue</ecommerce_revenue> + <ecommerce_revenue_evolution>Product Revenue Evolution</ecommerce_revenue_evolution> </columns> <reportData> @@ -45,6 +63,15 @@ <revenue>$ 10</revenue> <nb_actions>1</nb_actions> <nb_visits>1</nb_visits> + <visits_evolution>100%</visits_evolution> + <actions_evolution>100%</actions_evolution> + <revenue_evolution>$ 100%</revenue_evolution> + <nb_conversions>0</nb_conversions> + <nb_conversions_evolution>0</nb_conversions_evolution> + <orders>0</orders> + <orders_evolution>0</orders_evolution> + <ecommerce_revenue>$ 0</ecommerce_revenue> + <ecommerce_revenue_evolution>$ 0</ecommerce_revenue_evolution> </row> </result> <result prettyDate="Monday 4 January 2010"> @@ -52,13 +79,31 @@ <label>Site 1</label> <nb_actions>2</nb_actions> <nb_visits>1</nb_visits> + <visits_evolution>0%</visits_evolution> + <actions_evolution>100%</actions_evolution> + <revenue_evolution>$ -100%</revenue_evolution> <revenue>$ 0</revenue> + <nb_conversions>0</nb_conversions> + <nb_conversions_evolution>0</nb_conversions_evolution> + <orders>0</orders> + <orders_evolution>0</orders_evolution> + <ecommerce_revenue>$ 0</ecommerce_revenue> + <ecommerce_revenue_evolution>$ 0</ecommerce_revenue_evolution> </row> <row> <label>Site 2</label> <nb_actions>2</nb_actions> <nb_visits>1</nb_visits> + <visits_evolution>100%</visits_evolution> + <actions_evolution>100%</actions_evolution> <revenue>$ 0</revenue> + <revenue_evolution>$ 0</revenue_evolution> + <nb_conversions>0</nb_conversions> + <nb_conversions_evolution>0</nb_conversions_evolution> + <orders>0</orders> + <orders_evolution>0</orders_evolution> + <ecommerce_revenue>$ 0</ecommerce_revenue> + <ecommerce_revenue_evolution>$ 0</ecommerce_revenue_evolution> </row> </result> <result prettyDate="Tuesday 5 January 2010"> @@ -67,6 +112,15 @@ <revenue>$ 5</revenue> <nb_actions>5</nb_actions> <nb_visits>1</nb_visits> + <visits_evolution>0%</visits_evolution> + <actions_evolution>150%</actions_evolution> + <revenue_evolution>$ 100%</revenue_evolution> + <nb_conversions>0</nb_conversions> + <nb_conversions_evolution>0</nb_conversions_evolution> + <orders>0</orders> + <orders_evolution>0</orders_evolution> + <ecommerce_revenue>$ 0</ecommerce_revenue> + <ecommerce_revenue_evolution>$ 0</ecommerce_revenue_evolution> </row> </result> <result prettyDate="Wednesday 6 January 2010" /> diff --git a/tests/integration/expected/test_apiGetReportMetadata__API.getReportMetadata_day.xml b/tests/integration/expected/test_apiGetReportMetadata__API.getReportMetadata_day.xml index beae0bab91..4aaada689c 100644 --- a/tests/integration/expected/test_apiGetReportMetadata__API.getReportMetadata_day.xml +++ b/tests/integration/expected/test_apiGetReportMetadata__API.getReportMetadata_day.xml @@ -2,14 +2,61 @@ <result> <row> <category>All Websites</category> + <name>Single Websites dashboard</name> + <module>MultiSites</module> + <action>getOne</action> + <dimension>Website</dimension> + <metrics> + <nb_visits>Visits</nb_visits> + <visits_evolution>Visits Evolution</visits_evolution> + <nb_actions>Actions</nb_actions> + <actions_evolution>Actions Evolution</actions_evolution> + <revenue>Revenue</revenue> + <revenue_evolution>Revenue Evolution</revenue_evolution> + <nb_conversions>Conversions</nb_conversions> + <nb_conversions_evolution>Conversions Evolution</nb_conversions_evolution> + <orders>Ecommerce Orders</orders> + <orders_evolution>Ecommerce Orders Evolution</orders_evolution> + <ecommerce_revenue>Product Revenue</ecommerce_revenue> + <ecommerce_revenue_evolution>Product Revenue Evolution</ecommerce_revenue_evolution> + + </metrics> + <metricsDocumentation> + <nb_visits>If a visitor comes to your website for the first time or if he visits a page more than 30 minutes after his last page view, this will be recorded as a new visit.</nb_visits> + <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if he visits the website multiple times a day.</nb_uniq_visitors> + <nb_actions>The number of actions performed by your visitors. Actions can be page views, downloads or outlinks.</nb_actions> + <nb_actions_per_visit>The average number of actions (page views, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit> + <avg_time_on_site>The average duration of a visit.</avg_time_on_site> + <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate> + <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate> + <avg_time_on_page>The average amount of time visitors spent on this page (only the page, not the entire website).</avg_time_on_page> + <nb_hits>The number of times this page was visited.</nb_hits> + <exit_rate>The percentage of visits that left the website after viewing this page.</exit_rate> + + </metricsDocumentation> + <imageGraphUrl>index.php?module=API&method=ImageGraph.get&idSite=1&apiModule=MultiSites&apiAction=getOne&period=day&date=2009-01-04</imageGraphUrl> + <uniqueId>MultiSites_getOne</uniqueId> + + </row> + <row> + <category>All Websites</category> <name>All Websites dashboard</name> <module>MultiSites</module> <action>getAll</action> <dimension>Website</dimension> <metrics> <nb_visits>Visits</nb_visits> + <visits_evolution>Visits Evolution</visits_evolution> <nb_actions>Actions</nb_actions> + <actions_evolution>Actions Evolution</actions_evolution> <revenue>Revenue</revenue> + <revenue_evolution>Revenue Evolution</revenue_evolution> + <nb_conversions>Conversions</nb_conversions> + <nb_conversions_evolution>Conversions Evolution</nb_conversions_evolution> + <orders>Ecommerce Orders</orders> + <orders_evolution>Ecommerce Orders Evolution</orders_evolution> + <ecommerce_revenue>Product Revenue</ecommerce_revenue> + <ecommerce_revenue_evolution>Product Revenue Evolution</ecommerce_revenue_evolution> </metrics> <metricsDocumentation> diff --git a/tests/integration/expected/test_apiGetReportMetadata_year__API.getReportMetadata_year.xml b/tests/integration/expected/test_apiGetReportMetadata_year__API.getReportMetadata_year.xml index a7c8a72562..dcbbeb26ab 100644 --- a/tests/integration/expected/test_apiGetReportMetadata_year__API.getReportMetadata_year.xml +++ b/tests/integration/expected/test_apiGetReportMetadata_year__API.getReportMetadata_year.xml @@ -2,14 +2,61 @@ <result> <row> <category>All Websites</category> + <name>Single Websites dashboard</name> + <module>MultiSites</module> + <action>getOne</action> + <dimension>Website</dimension> + <metrics> + <nb_visits>Visits</nb_visits> + <visits_evolution>Visits Evolution</visits_evolution> + <nb_actions>Actions</nb_actions> + <actions_evolution>Actions Evolution</actions_evolution> + <revenue>Revenue</revenue> + <revenue_evolution>Revenue Evolution</revenue_evolution> + <nb_conversions>Conversions</nb_conversions> + <nb_conversions_evolution>Conversions Evolution</nb_conversions_evolution> + <orders>Ecommerce Orders</orders> + <orders_evolution>Ecommerce Orders Evolution</orders_evolution> + <ecommerce_revenue>Product Revenue</ecommerce_revenue> + <ecommerce_revenue_evolution>Product Revenue Evolution</ecommerce_revenue_evolution> + + </metrics> + <metricsDocumentation> + <nb_visits>If a visitor comes to your website for the first time or if he visits a page more than 30 minutes after his last page view, this will be recorded as a new visit.</nb_visits> + <nb_uniq_visitors>The number of unduplicated visitors coming to your website. Every user is only counted once, even if he visits the website multiple times a day.</nb_uniq_visitors> + <nb_actions>The number of actions performed by your visitors. Actions can be page views, downloads or outlinks.</nb_actions> + <nb_actions_per_visit>The average number of actions (page views, downloads or outlinks) that were performed during the visits.</nb_actions_per_visit> + <avg_time_on_site>The average duration of a visit.</avg_time_on_site> + <bounce_rate>The percentage of visits that only had a single pageview. This means, that the visitor left the website directly from the entrance page.</bounce_rate> + <conversion_rate>The percentage of visits that triggered a goal conversion.</conversion_rate> + <avg_time_on_page>The average amount of time visitors spent on this page (only the page, not the entire website).</avg_time_on_page> + <nb_hits>The number of times this page was visited.</nb_hits> + <exit_rate>The percentage of visits that left the website after viewing this page.</exit_rate> + + </metricsDocumentation> + <imageGraphUrl>index.php?module=API&method=ImageGraph.get&idSite=1&apiModule=MultiSites&apiAction=getOne&period=year&date=2009-01-04</imageGraphUrl> + <uniqueId>MultiSites_getOne</uniqueId> + + </row> + <row> + <category>All Websites</category> <name>All Websites dashboard</name> <module>MultiSites</module> <action>getAll</action> <dimension>Website</dimension> <metrics> <nb_visits>Visits</nb_visits> + <visits_evolution>Visits Evolution</visits_evolution> <nb_actions>Actions</nb_actions> + <actions_evolution>Actions Evolution</actions_evolution> <revenue>Revenue</revenue> + <revenue_evolution>Revenue Evolution</revenue_evolution> + <nb_conversions>Conversions</nb_conversions> + <nb_conversions_evolution>Conversions Evolution</nb_conversions_evolution> + <orders>Ecommerce Orders</orders> + <orders_evolution>Ecommerce Orders Evolution</orders_evolution> + <ecommerce_revenue>Product Revenue</ecommerce_revenue> + <ecommerce_revenue_evolution>Product Revenue Evolution</ecommerce_revenue_evolution> </metrics> <metricsDocumentation> diff --git a/tests/integration/expected/test_noVisit_PeriodIsLast__MultiSites.getOne_day.xml b/tests/integration/expected/test_noVisit_PeriodIsLast__MultiSites.getOne_day.xml new file mode 100644 index 0000000000..782215d83c --- /dev/null +++ b/tests/integration/expected/test_noVisit_PeriodIsLast__MultiSites.getOne_day.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8" ?> +<results> + <result date="2009-01-04" /> + <result date="2009-01-05" /> + <result date="2009-01-06" /> + <result date="2009-01-07" /> + <result date="2009-01-08" /> + <result date="2009-01-09" /> + <result date="2009-01-10" /> +</results> diff --git a/tests/integration/expected/test_noVisit_PeriodIsLast__MultiSites.getOne_week.xml b/tests/integration/expected/test_noVisit_PeriodIsLast__MultiSites.getOne_week.xml new file mode 100644 index 0000000000..24f12bc1f7 --- /dev/null +++ b/tests/integration/expected/test_noVisit_PeriodIsLast__MultiSites.getOne_week.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8" ?> +<results> + <result date="From 2008-12-29 to 2009-01-04" /> + <result date="From 2009-01-05 to 2009-01-11" /> + <result date="From 2009-01-12 to 2009-01-18" /> + <result date="From 2009-01-19 to 2009-01-25" /> + <result date="From 2009-01-26 to 2009-02-01" /> + <result date="From 2009-02-02 to 2009-02-08" /> + <result date="From 2009-02-09 to 2009-02-15" /> +</results> diff --git a/tests/integration/expected/test_noVisit__MultiSites.getOne_day.xml b/tests/integration/expected/test_noVisit__MultiSites.getOne_day.xml new file mode 100644 index 0000000000..93a65e6e8a --- /dev/null +++ b/tests/integration/expected/test_noVisit__MultiSites.getOne_day.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8" ?> +<result /> |