';
+ $filters[] = $select;
+
+ // dashboard only has account selection, no room for anything else
+ // only shows main selection drop downs on analytics view
+ if ($allFilters)
+ {
+ $select = '
';
+ $filters[] = $select;
+
+ // add a click to update link
+ $filters[] = ' [' . JText::_('COM_SH404SEF_CHECK_ANALYTICS') . ']';
+
+ }
+ else
+ {
+ // on dashboard, there is no date select, so we must display the date range
+ $filters[] = ' ' . JText::_('COM_SH404SEF_ANALYTICS_DATE_RANGE') . '
';
+ }
+ }
+ // use layout to render
+ return $filters;
+ }
+
+ protected function _extractAuthToken($body)
+ {
+ $SID = explode('LSID=', $body);
+ $this->_SID = trim($SID[0]);
+ $this->_SID = ltrim($this->_SID, 'SID=');
+
+ $LSID = explode('Auth=', $SID[1]);
+ $this->_LSID = trim($LSID[0]);
+
+ $this->_Auth = trim($LSID[1]);
+ }
+}
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/analyticsgareportdashboard.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/analyticsgareportdashboard.php
new file mode 100644
index 00000000..83753167
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/analyticsgareportdashboard.php
@@ -0,0 +1,1013 @@
+_baseChartPath = sh404SEF_ADMIN_ABS_PATH . 'lib/pChart';
+
+ }
+
+ /**
+ *
+ * @param object $config
+ * @param array $options
+ * @param object $client
+ */
+ public function fetchData( $config, $options, $Auth, $endPoint, $appKey) {
+
+ // store parameters
+ $this->_config = $config;
+ $this->_options = $options;
+ $this->_Auth = $Auth;
+ $this->_endPoint = $endPoint;
+ $this->_appKey = $appKey;
+
+ // get a http client
+ $this->_client = Sh404sefHelperAnalytics::getHttpClient();
+
+ // response object
+ $this->_formatedResponse = new stdClass();
+
+ // read data from Google
+ $this->_getData();
+
+ // processing of global data always happens
+ $this->_addFiguresVisits();
+
+ // call methods to create each of the report sub parts
+ switch ($this->_options['subrequest']) {
+
+ case 'visits':
+ // create a graph of visits
+ $this->_formatedResponse->images['visits'] = $this->_createVisitsGraph();
+ break;
+
+ case 'sources':
+ // create a graph of traffic sources
+ $this->_formatedResponse->images['sources'] = $this->_createSourcesGraph();
+ break;
+
+ case 'perf':
+ $this->_addCustomVars();
+ break;
+
+ case 'top5referrers':
+ $this->_addFiguresReferrers();
+ break;
+
+ case 'top5urls':
+ $this->_addFiguresUrls();
+ break;
+
+ case 'topsocialfb':
+ $this->_addFiguresSocialFB();
+ break;
+ case 'topsocialtweeter':
+ $this->_addFiguresSocialTweeter();
+ break;
+ case 'topsocialpinterest':
+ $this->_addFiguresSocialPinterest();
+ break;
+ case 'topsocialplusone':
+ $this->_addFiguresSocialPlusOne();
+ break;
+ case 'topsocialplusonepage':
+ $this->_addFiguresSocialPlusOnePage();
+ break;
+
+ }
+
+ // send back to caller
+ return $this->_formatedResponse;
+
+ }
+
+ /**
+ * Make queries to Google API as needed
+ * to collect data required to build report
+ *
+ * The final report page is built using several ajax calls. The main dashboard template
+ * is first displayed, with only place holders for the various parts
+ * Then some javascript fired with the main dashboard display
+ * performs several requests, or "subrequests", each asking for a specific set
+ * of data or graphic
+ * Reason for this is to speed things up, as the whole request-google-respond-prepare graph
+ * can last 2 or 3 seconds. As there may be 6 or 7 requests, this adds up to more than 15 seconds
+ * Using several ajax calls allow doing all the requests in parallel, and the total time is
+ * not much longer than the time for the longest individual subrequest
+ * On the contrary, some delay between subrequests has been introduced
+ * as Google has/may refuse serving them if they exceed allowances, published as
+ * 4 concurrent requests max
+ * 10 req. per sec max
+ *
+ */
+ protected function _getData() {
+
+ // need config, to know which data user wants to display : visits, unique visitors, pageviews
+ $sefConfig = Sh404sefFactory::getConfig();
+
+ // set headers required by Google Analytics
+ $headers = array(
+ 'GData-Version' => 2
+ , 'Authorization' => 'GoogleLogin auth=' . $this->_Auth
+ );
+
+ $this->_client->setHeaders( $headers);
+
+ // 0 . Global data, needed in all queries to calculate %
+
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => ''
+ , 'metrics' => 'ga:pageviews,ga:visits,ga:visitors,ga:bounces,ga:entrances,ga:timeOnSite,ga:newVisits'
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ , 'key' => $this->_appKey
+ );
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'global');
+
+ // 0bis . Global social data, needed in all queries to calculate %
+
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => 'ga:eventCategory,ga:eventAction'
+ , 'metrics' => 'ga:totalEvents,ga:visits'
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'globalSocial');
+
+ // specific queries for each subrequest
+ switch ( $this->_options['subrequest']) {
+
+ case 'visits':
+
+ // 1 . query pageviews and visits
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => $this->_options['groupBy']
+ , 'metrics' => $sefConfig->analyticsDashboardDataType
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'visits');
+
+ break;
+
+ case 'sources':
+
+ // 2 . traffic sources
+
+ // Google does not allow combining dimension=ga:medium with metric = unique visitors
+ $metric = $sefConfig->analyticsDashboardDataType == 'ga:visitors' ?
+ 'ga:visits' : $sefConfig->analyticsDashboardDataType;
+
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => 'ga:medium'
+ , 'metrics' => $metric
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'sources');
+
+ break;
+
+ case 'perf':
+
+ // 4 . Custom var : logged in users
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => 'ga:customVarValue' . SH404SEF_ANALYTICS_USER_CUSTOM_VAR
+ , 'metrics' => 'ga:pageviews'
+ , 'sort' => '-ga:pageviews'
+ , 'max-results' => '100'
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'logged-in-users');
+
+ // 5 . Custom var : page creation time
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => 'ga:customVarValue' . SH404SEF_ANALYTICS_TIME_CUSTOM_VAR
+ , 'metrics' => 'ga:pageviews'
+ , 'sort' => '-ga:pageviews'
+ , 'max-results' => '512'
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'page-creation-time');
+
+ break;
+
+ case 'top5urls':
+
+ // 6 . Top 5 urls
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => 'ga:pagePath'
+ , 'metrics' => 'ga:pageviews,ga:timeOnPage'
+ , 'sort' => '-ga:pageviews'
+ , 'max-results' => $this->_options['max-top-urls']
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'top5urls');
+
+ break;
+
+ case 'top5referrers':
+
+ // 7 . Top 5 referrer
+
+ // Google does not allow combining dimension=ga:medium with metric = unique visitors
+ $metric = $sefConfig->analyticsDashboardDataType == 'ga:visitors' ?
+ 'ga:visits' : $sefConfig->analyticsDashboardDataType;
+
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => 'ga:source,ga:referralPath'
+ , 'metrics' => $metric . ',ga:timeOnSite,ga:bounces,ga:newVisits'
+ , 'sort' => '-' . $metric
+ , 'max-results' => $this->_options['max-top-referrers']
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'top5referrers');
+
+ break;
+
+ case 'topsocialfb':
+
+ // 8 . topSocial
+
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => 'ga:eventCategory,ga:eventLabel,ga:eventAction'
+ , 'metrics' => 'ga:totalEvents,ga:pageviews,ga:timeOnPage'
+ , 'sort' => '-' . 'ga:totalEvents'
+ , 'filters' => 'ga:eventCategory==sh404SEF_social_tracker_facebook'
+ , 'max-results' => $this->_options['max-top-referrers']
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'topSocialFB');
+
+ break;
+ case 'topsocialtweeter':
+
+ // 8 . topSocial
+
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => 'ga:eventCategory,ga:eventLabel,ga:eventAction'
+ , 'metrics' => 'ga:totalEvents,ga:pageviews,ga:timeOnPage'
+ , 'sort' => '-' . 'ga:totalEvents'
+ , 'filters' => 'ga:eventCategory==sh404SEF_social_tracker_tweeter'
+ , 'max-results' => $this->_options['max-top-referrers']
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'topSocialTweeter');
+
+ break;
+
+ case 'topsocialpinterest':
+
+ // 8 . topSocial
+
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => 'ga:eventCategory,ga:eventLabel,ga:eventAction'
+ , 'metrics' => 'ga:totalEvents,ga:pageviews,ga:timeOnPage'
+ , 'sort' => '-' . 'ga:totalEvents'
+ , 'filters' => 'ga:eventCategory==sh404SEF_social_tracker_pinterest'
+ , 'max-results' => $this->_options['max-top-referrers']
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'topSocialPinterest');
+
+ break;
+
+ case 'topsocialplusone':
+
+ // 8 . topSocial
+
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => 'ga:eventCategory,ga:eventLabel,ga:eventAction'
+ , 'metrics' => 'ga:totalEvents,ga:pageviews,ga:timeOnPage'
+ , 'sort' => '-' . 'ga:totalEvents'
+ , 'filters' => 'ga:eventCategory==sh404SEF_social_tracker_gplus'
+ , 'max-results' => $this->_options['max-top-referrers']
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'topSocialPlusOne');
+
+ break;
+ case 'topsocialplusonepage':
+
+ // 8 . topSocial
+
+ // build uri as needed to retrieve required data
+ $query = array( 'dimensions' => 'ga:eventCategory,ga:eventLabel,ga:eventAction'
+ , 'metrics' => 'ga:totalEvents,ga:pageviews,ga:timeOnPage'
+ , 'sort' => '-' . 'ga:totalEvents'
+ , 'filters' => 'ga:eventCategory==sh404SEF_social_tracker_gplus_page'
+ , 'max-results' => $this->_options['max-top-referrers']
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ );
+
+
+ // use method to build the correct url
+ $uri = $this->_buildQueryUri( $query);
+
+ // set target API url
+ $this->_client->setUri( $uri);
+
+ // perform query
+ $this->_query( 'topSocialPlusOnePage');
+
+ break;
+
+ }
+
+ }
+
+ protected function _addFiguresVisits() {
+
+ if (!empty($this->_rawData['global'][0])) {
+ $this->_formatedResponse->global = $this->_rawData['global'][0];
+ $this->_formatedResponse->global->bounceRate = empty($this->_formatedResponse->global->entrances) ? 0 : $this->_formatedResponse->global->bounces / $this->_formatedResponse->global->entrances;
+ $this->_formatedResponse->global->avgTimeOnSite = empty($this->_formatedResponse->global->visits) ? 0 : $this->_formatedResponse->global->timeOnSite / $this->_formatedResponse->global->visits;
+ $this->_formatedResponse->global->newVisitsPerCent = empty($this->_formatedResponse->global->visits) ? 0 : $this->_formatedResponse->global->newVisits / $this->_formatedResponse->global->visits;
+ $this->_formatedResponse->global->pagesPerVisit = empty($this->_formatedResponse->global->visits) ? 0 : $this->_formatedResponse->global->pageviews / $this->_formatedResponse->global->visits;
+ } else {
+ $this->_formatedResponse->global = new stdClass();
+ $this->_formatedResponse->global->visits = 0;
+ $this->_formatedResponse->global->visitors = 0;
+ $this->_formatedResponse->global->pageviews = 0;
+ $this->_formatedResponse->global->bounceRate = 0;
+ $this->_formatedResponse->global->avgTimeOnSite = 0;
+ $this->_formatedResponse->global->newVisitsPerCent = 0;
+ $this->_formatedResponse->global->pagesPerVisit = 0;
+ }
+ // add social engagement global values
+ $this->_formatedResponse->global->totalSocialEvents = 0;
+ $this->_formatedResponse->global->sh404SEF_social_tracker_facebook = 0;
+ $this->_formatedResponse->global->sh404SEF_social_tracker_gplus = 0;
+ $this->_formatedResponse->global->sh404SEF_social_tracker_tweeter = 0;
+ $this->_formatedResponse->global->sh404SEF_social_tracker_pinterest = 0;
+ $this->_formatedResponse->global->sh404SEF_social_tracker_gplus_page = 0;
+ $this->_formatedResponse->global->visitsWithSocialEngagement = 0;
+ if(!empty( $this->_rawData['globalSocial'])) {
+ foreach($this->_rawData['globalSocial'] as $entry) {
+ $cat = $entry->dimension['eventCategory'];
+ $action = $entry->dimension['eventAction'];
+ switch($entry->dimension['eventCategory']) {
+ case 'sh404SEF_social_tracker_facebook':
+ $this->_formatedResponse->global->visitsWithSocialEngagement += $entry->visits;
+ if($action == 'like') {
+ $this->_formatedResponse->global->totalSocialEvents += $entry->totalEvents;
+ $this->_formatedResponse->global->$cat += $entry->totalEvents;
+ } else {
+ $this->_formatedResponse->global->totalSocialEvents -= $entry->totalEvents;
+ $this->_formatedResponse->global->$cat -= $entry->totalEvents;
+ }
+ break;
+ case 'sh404SEF_social_tracker_gplus':
+ $this->_formatedResponse->global->visitsWithSocialEngagement += $entry->visits;
+ if($action == 'on') {
+ $this->_formatedResponse->global->totalSocialEvents += $entry->totalEvents;
+ $this->_formatedResponse->global->$cat += $entry->totalEvents;
+ } else {
+ $this->_formatedResponse->global->totalSocialEvents -= $entry->totalEvents;
+ $this->_formatedResponse->global->$cat -= $entry->totalEvents;
+ }
+ break;
+ case 'sh404SEF_social_tracker_tweeter':
+ case 'sh404SEF_social_tracker_pinterest':
+ case 'sh404SEF_social_tracker_gplus_page':
+ $this->_formatedResponse->global->visitsWithSocialEngagement += $entry->visits;
+ $this->_formatedResponse->global->totalSocialEvents += $entry->totalEvents;
+ $this->_formatedResponse->global->$cat += $entry->totalEvents;
+ break;
+ }
+ }
+ $this->_formatedResponse->global->visitsWithSocialEngagement =
+ empty($this->_formatedResponse->global->visits) ? 0 : $this->_formatedResponse->global->visitsWithSocialEngagement / $this->_formatedResponse->global->visits;
+ }
+
+ }
+
+ protected function _addFiguresUrls() {
+
+ $this->_formatedResponse->top5urls = array();
+ foreach( $this->_rawData['top5urls'] as $entry) {
+ $entry->avgTimeOnPage = empty($entry->pageviews) ? 0 : $entry->timeOnPage / $entry->pageviews;
+ $entry->pageviewsPerCent = empty($this->_formatedResponse->global->pageviews) ? 0 : $entry->pageviews / $this->_formatedResponse->global->pageviews;
+ $this->_formatedResponse->top5urls[] = $entry;
+ }
+
+ }
+
+ protected function _addFiguresSocialFB() {
+
+ $this->_formatedResponse->topSocialFB = array();
+ foreach( $this->_rawData['topSocialFB'] as $entry) {
+ $entry->avgTimeOnPage = empty($entry->pageviews) ? 0 : $entry->timeOnPage / $entry->pageviews;
+ $entry->eventsPerCent = empty($this->_formatedResponse->global->totalSocialEvents) ? 0 : $entry->totalEvents / $this->_formatedResponse->global->totalSocialEvents;
+ $this->_formatedResponse->topSocialFB[] = $entry;
+ }
+
+ }
+
+ protected function _addFiguresSocialTweeter() {
+
+ $this->_formatedResponse->topSocialTweeter = array();
+ foreach( $this->_rawData['topSocialTweeter'] as $entry) {
+ $entry->avgTimeOnPage = empty($entry->pageviews) ? 0 : $entry->timeOnPage / $entry->pageviews;
+ $entry->eventsPerCent = empty($this->_formatedResponse->global->totalSocialEvents) ? 0 : $entry->totalEvents / $this->_formatedResponse->global->totalSocialEvents;
+ $this->_formatedResponse->topSocialTweeter[] = $entry;
+ }
+
+ }
+
+ protected function _addFiguresSocialPinterest() {
+
+ $this->_formatedResponse->topSocialPinterest = array();
+ foreach( $this->_rawData['topSocialPinterest'] as $entry) {
+ $entry->avgTimeOnPage = empty($entry->pageviews) ? 0 : $entry->timeOnPage / $entry->pageviews;
+ $entry->eventsPerCent = empty($this->_formatedResponse->global->totalSocialEvents) ? 0 : $entry->totalEvents / $this->_formatedResponse->global->totalSocialEvents;
+ $this->_formatedResponse->topSocialPinterest[] = $entry;
+ }
+
+ }
+
+ protected function _addFiguresSocialPlusOne() {
+
+ $this->_formatedResponse->topSocialPlusOne = array();
+ foreach( $this->_rawData['topSocialPlusOne'] as $entry) {
+ $entry->avgTimeOnPage = empty($entry->pageviews) ? 0 : $entry->timeOnPage / $entry->pageviews;
+ $entry->eventsPerCent = empty($this->_formatedResponse->global->totalSocialEvents) ? 0 : $entry->totalEvents / $this->_formatedResponse->global->totalSocialEvents;
+ $this->_formatedResponse->topSocialPlusOne[] = $entry;
+ }
+
+ }
+
+ protected function _addFiguresSocialPlusOnePage() {
+
+ $this->_formatedResponse->topSocialPlusOnePage = array();
+ foreach( $this->_rawData['topSocialPlusOnePage'] as $entry) {
+ $entry->avgTimeOnPage = empty($entry->pageviews) ? 0 : $entry->timeOnPage / $entry->pageviews;
+ $entry->eventsPerCent = empty($this->_formatedResponse->global->totalSocialEvents) ? 0 : $entry->totalEvents / $this->_formatedResponse->global->totalSocialEvents;
+ $this->_formatedResponse->topSocialPlusOnePage[] = $entry;
+ }
+
+ }
+
+ protected function _addFiguresReferrers() {
+
+ // need config, to know which data user wants to display : visits, unique visitors, pageviews
+ $sefConfig = Sh404sefFactory::getConfig();
+ // Google does not allow combining dimension=ga:medium with metric = unique visitors
+ $metric = $sefConfig->analyticsDashboardDataType == 'ga:visitors' ?
+ 'ga:visits' : $sefConfig->analyticsDashboardDataType;
+
+ $dataTypeString = str_replace( 'ga:', '', $metric);
+
+ $this->_formatedResponse->top5referrers = array();
+ foreach( $this->_rawData['top5referrers'] as $entry) {
+ $entry->views = $entry->$dataTypeString;
+ $entry->avgTimeOnSite = empty($entry->$dataTypeString) ? 0 : $entry->timeOnSite / $entry->$dataTypeString;
+ $entry->bounceRate = empty($entry->$dataTypeString) ? 0 : $entry->bounces / $entry->$dataTypeString;
+ $entry->viewsPerCent = empty($this->_formatedResponse->global->$dataTypeString) ? 0 : $entry->$dataTypeString / $this->_formatedResponse->global->$dataTypeString;
+ $this->_formatedResponse->top5referrers[] = $entry;
+ }
+
+
+ }
+
+ protected function _addCustomVars() {
+
+ $this->_formatedResponse->perf = new stdClass();
+
+ // prepare time and memory calculation
+ $field = 'customVarValue' . SH404SEF_ANALYTICS_TIME_CUSTOM_VAR;
+ $totalPages = 0;
+ $totalTime = 0;
+ $totalMemory = 0;
+
+ // now iterate over time and memory records
+ foreach( $this->_rawData['page-creation-time'] as $entry) {
+ $record = intval( $entry->dimension[$field]); // make it an int, this will remove dev data that had decimals
+
+ // sanitize
+ $record = $record < 3 ? 0 : $record;
+
+ // we only calculate averaged on pages we have time and ram data for
+ if (!empty( $record)) {
+ // separate time and memory
+ $time = $record >> 4; // bits 5 and up
+ $memory = $record & 15; // bits 1 to 4
+
+ // aggregate
+ $totalPages += $entry->pageviews;
+ $totalTime += Sh404sefHelperAnalytics::declassifyTime( $time) * $entry->pageviews;
+ $totalMemory += Sh404sefHelperAnalytics::declassifyMemory( $memory) * $entry->pageviews;
+
+ }
+ }
+
+ // calculate averages time and ram now
+ $this->_formatedResponse->perf->avgPageCreationTime = empty($totalPages) ? 0 : $totalTime / $totalPages;
+ $this->_formatedResponse->perf->avgMemoryUsed = empty($totalPages) ? 0 : $totalMemory / $totalPages;
+
+ // logged in users handling
+ $field = 'customVarValue' . SH404SEF_ANALYTICS_USER_CUSTOM_VAR;
+ $groups = Sh404sefHelperGeneral::getUserGroups('title');
+ $loggedInPages = 0;
+ foreach( $this->_rawData['logged-in-users'] as $entry) {
+ $userType = urldecode( $entry->dimension[$field]);
+ // if user was logged in, any group, including "Public Frontend"
+ if (in_array( $userType, $groups)) {
+ // include # of pages
+ $loggedInPages += $entry->pageviews;
+ }
+ }
+
+ $this->_formatedResponse->perf->loggedInUserRate = empty($this->_formatedResponse->global->pageviews) ? 0 : $loggedInPages / $this->_formatedResponse->global->pageviews;
+
+ }
+
+
+ protected function _createVisitsGraph() {
+
+ // load pChart graphic library
+ $this->_loadLibs();
+
+ // définition des données à afficher
+ $dataSet = new pData();
+
+ // need config, to know which data user wants to display : visits, unique visitors, pageviews
+ $sefConfig = Sh404sefFactory::getConfig();
+ $dataTypeString = str_replace( 'ga:', '', $sefConfig->analyticsDashboardDataType);
+
+ // get data from response
+ $data = array();
+ $maxY = 0;
+ foreach( $this->_rawData['visits'] as $entry) {
+ $data[] = $entry->$dataTypeString;
+ if($entry->$dataTypeString > $maxY) {
+ $maxY = $entry->$dataTypeString;
+ }
+ }
+
+ // format dates
+ $dates = Sh404sefHelperAnalytics::formatAbciseDates( $this->_rawData['visits'], $this->_options);
+
+ $dataSet->AddPoint($data, $dataTypeString);
+ $dataSet->AddPoint($dates,"dates");
+ $dataSet->addSerie($dataTypeString);
+ $dataSet->SetAbsciseLabelSerie("dates");
+ $label = JText::_( 'COM_SH404SEF_ANALYTICS_DATA_' . strtoupper( $dataTypeString));
+ $dataSet->SetSerieName( $label, $dataTypeString);
+
+ // Initialise the graph
+ $w = $this->_options['cpWidth'];
+ $w = empty( $w) ? 400 : intval($w - 40);
+ $h = 225;
+ $centreX = intval(0.50 * $w);
+ $centreY = intval(0.5 * $h);
+ $chart = new pChart( $w, $h);
+ $fontSize = 8;
+ // calculate left margin based on max value to display
+ $leftMargin = 20 + $fontSize + 20 + $fontSize * strlen( $maxY);
+ $bottomMargin = 5 + $fontSize * 6;
+
+ switch ($this->_options['groupBy']) {
+ case 'ga:year,ga:month,ga:week,ga:day':
+ $YAxisName = JText::_( 'Day');
+ break;
+ // date string represents a week number
+ case 'ga:year,ga:month,ga:week':
+ $YAxisName = JText::_( 'Week');
+ break;
+ case 'ga:year,ga:month':
+ $YAxisName = JText::_( 'Month');
+ break;
+ }
+
+ $dataSet->SetYAxisName( $label . JText::_('COM_SH404SEF_ANALYTICS_REPORT_PER_LABEL') . $YAxisName);
+ $chart->setFontProperties( $this->_baseChartPath . '/Fonts/arial.ttf',$fontSize);
+ $chart->setGraphArea( $leftMargin,30,$w-20,$h-$bottomMargin);
+ $chart->drawFilledRoundedRectangle(7,7,$w-7,$h-7,5,240,240,240);
+ $chart->drawRoundedRectangle(5,5,$w-5,$h-5,5,230,230,230);
+ $chart->drawGraphArea(255,255,255,TRUE);
+ $d = $dataSet->GetData();
+ if (!empty( $d)) {
+ $chart->drawScale($d,$dataSet->GetDataDescription(),SCALE_NORMAL,150,150,150,TRUE,60,0, false);
+ $chart->drawGrid(4,TRUE,230,230,230,50);
+
+ // Draw the 0 line
+ $chart->setFontProperties( $this->_baseChartPath . '/Fonts/arial.ttf',$fontSize);
+
+ $chart->drawTreshold(0,143,55,72,TRUE,TRUE);
+
+ // Draw the line graph
+ $chart->drawLineGraph($d,$dataSet->GetDataDescription());
+ $chart->drawPlotGraph($d, $dataSet->GetDataDescription(),3,2,255,255,255);
+ }
+
+ // create a temporary file for
+ $user = JFactory::getUser();
+
+ // make sure the root tmp dir exists
+ Sh404sefHelperFiles::createDirAndIndex( JPATH_ROOT . '/' . $this->_tmpImagesPath);
+
+ // file name is per user
+ $basePath = $this->_tmpImagesPath . '/' . md5( 'useless_' . $user->id . '_hashing') . '/';
+
+ // create path and make sure there's an index.html file in it
+ Sh404sefHelperFiles::createDirAndIndex( JPATH_ROOT . '/' . $basePath);
+
+ $imageFileName = Sh404sefHelperAnalytics::createTempFile( $basePath, $this->_options['report'] . '.' . $this->_options['accountId'] . '.visits.' . $dataTypeString);
+ $chart->Render( JPATH_ROOT . '/' . $imageFileName);
+
+ // need cleaning up, so let's remove all report files for that user older than an hour or so
+ Sh404sefHelperAnalytics::cleanReportsImageFiles( JPATH_ROOT . '/' . $basePath, $age = 4000);
+
+ return JURI::root() . $imageFileName;
+ }
+
+ protected function _createSourcesGraph() {
+
+ // load pChart graphic library
+ $this->_loadLibs();
+
+ // définition des données à afficher
+ $dataSet = new pData();
+
+ // need config, to know which data user wants to display : visits, unique visitors, pageviews
+ $sefConfig = Sh404sefFactory::getConfig();
+
+ // Google does not allow combining dimension=ga:medium with metric = unique visitors
+ $dataType = $sefConfig->analyticsDashboardDataType == 'ga:visitors' ?
+ 'ga:visits' : $sefConfig->analyticsDashboardDataType;
+ $dataTypeString = str_replace( 'ga:', '', $dataType);
+
+ // sort data for proper display
+ usort( $this->_rawData['sources'], array($this, '_sortSourcesDataCompareFunction'));
+
+ // we walk the array, pulling out alternatively
+ // the first and last items
+ // making the new array having the largest item
+ // followed by the smallest, then second largest
+ // then second smallest, ...
+ // which makes drawing labels much easier
+ $tmpArray = array();
+ $even = false;
+ $max = count($this->_rawData['sources']);
+ for( $i = 0; $i < $max; $i++) {
+ if ($even) {
+ // pull last item in sorted array
+ $tmpArray[] = array_pop( $this->_rawData['sources']);
+ } else {
+ // pull array first item
+ $tmpArray[] = array_shift( $this->_rawData['sources']);
+ }
+ // flag inversion
+ $even = !$even;
+ };
+
+ // get data from response
+ $data = array();
+ $types = array();
+ foreach( $tmpArray as $entry) {
+ $value = $entry->$dataTypeString;
+ // do not add empty values, as pChart would choke on that and display a warning
+ if( !empty($value)) {
+ $data[] = $value;
+ $types[] = Sh404sefHelperAnalytics::getReferralLabel( $entry->dimension['medium']);
+ }
+ }
+
+ $dataSet->AddPoint( $data,"visits");
+ $dataSet->AddPoint( $types,"types");
+ $dataSet->addSerie('visits');
+ $dataSet->SetAbsciseLabelSerie("types");
+ $label = JText::_( 'COM_SH404SEF_ANALYTICS_REPORT_SOURCES') . JText::_('COM_SH404SEF_ANALYTICS_REPORT_BY_LABEL') . Sh404sefHelperAnalytics::getDataTypeTitle();
+ $dataSet->SetSerieName( $label,"visits");
+
+ // Initialise the graph
+ $w = intval( 0.45 * $this->_options['cpWidth']);
+ $w = empty( $w) ? 160 : $w;
+ $radius = intval( $w * 0.22);
+ $margin = 5;
+ $h = intval($w * 0.8);
+ $centreX = intval(0.50 * $w);
+ $centreY = intval(0.50 * $h);
+ $chart = new pChart( $w, $h);
+ $fontSize = 8;
+
+ // prepare graph
+ $chart->setFontProperties( $this->_baseChartPath . '/' .'Fonts/arial.ttf', $fontSize);
+ $chart->loadColorPalette( $this->_baseChartPath . '/' . 'palettes' . '/' . 'tones-2-green-soft.php');
+ $chart->setGraphArea( $margin, $margin, $w-$margin, $h-$margin);
+
+ // This will draw a shadow under the pie chart
+ $chart->drawFilledCircle( $centreX + 4, $centreY + 4, $radius,200,200,200);
+
+ // Draw the pie chart
+ $d = $dataSet->GetData();
+ if (!empty( $d)) {
+ $chart->drawBasicPieGraph( $d, $dataSet->GetDataDescription(), $centreX, $centreY, $radius, PIE_PERCENTAGE_LABEL_VALUE, 255, 255, 218);
+ }
+
+ // create a temporary file for
+ $user = JFactory::getUser();
+
+ // make sure the root tmp dir exists
+ Sh404sefHelperFiles::createDirAndIndex( JPATH_ROOT . '/' . $this->_tmpImagesPath);
+
+ // file name is variable to avoid browser cache
+ $basePath = $this->_tmpImagesPath . '/' . md5( 'useless_' . $user->id . '_hashing') . '/';
+
+ // create path and make sure there's an index.html file in it
+ Sh404sefHelperFiles::createDirAndIndex( JPATH_ROOT . '/' . $basePath);
+
+ $imageFileName = Sh404sefHelperAnalytics::createTempFile( $basePath, $this->_options['report'] . '.' . $this->_options['accountId']. '.sources.' . $dataTypeString);
+ $chart->Render( JPATH_ROOT. '/' . $imageFileName);
+
+ // need cleaning up, so let's remove all report files for that user older than an hour or so
+ Sh404sefHelperAnalytics::cleanReportsImageFiles( JPATH_ROOT . '/' . $basePath, $age = 4000);
+
+ return JURI::root() . $imageFileName;
+
+ }
+
+ /**
+ * Builds an URI suitable to query Google Analytics API
+ * @param array $query a set of query constraints (ie: dimensions, metrics, etc)
+ */
+ protected function _buildQueryUri( $query) {
+
+ // default values
+ $defaultQuery = array(
+ 'ids' => 'ga:' . $this->_options['accountId']
+ , 'dimensions' => array()
+ , 'metrics' => array()
+ , 'sort' => null
+ , 'filters' => null
+ , 'segment' => null
+ , 'start-date' => $this->_options['startDate']
+ , 'end-date' => $this->_options['endDate']
+ , 'start-index' => null
+ , 'max-results' => $this->_options['max-results']
+ );
+
+ // combine with request
+ $query = array_merge( $defaultQuery, $query);
+
+ // combine them into a uri
+ $uri = '';
+ foreach( $query as $key => $value) {
+ if (!is_null( $value)) {
+ $uri .= '&' . $key . '=' . $value;
+ }
+ }
+
+ // prepare dimensions
+ $uri = $this->_endPoint . 'data?' . ltrim( $uri, '&');
+
+ return $uri;
+ }
+
+ protected function _query( $type) {
+
+ //perform request
+ $response = $this->_client->request();
+
+ // check if authentified
+ Sh404sefHelperAnalytics::verifyAuthResponse( $response);
+
+ // analyze response
+ $parser = new DOMDocument();
+ $parser->loadXML( $response->getBody());
+ $this->_rawData[$type] = $parser->getElementsByTagName('entry');
+ $this->_decodeXml( $type);
+ }
+
+ protected function _decodeXml($type) {
+
+ $data = array();
+
+ foreach( $this->_rawData[$type] as $entry) {
+ $r = new StdClass();
+
+ $dimensions = $entry->getElementsByTagName('dimension');
+ foreach($dimensions as $dimension) {
+ $name = $dimension->getAttribute('name');
+ $name = str_replace( 'ga:', '', $name);
+ $r->dimension[$name] = $dimension->getAttribute('value');
+ }
+ $metrics = $entry->getElementsByTagName('metric');
+ foreach( $metrics as $metric) {
+ $name = $metric->getAttribute('name');
+ $name = str_replace( 'ga:', '', $name);
+ $r->$name = $metric->getAttribute('value');
+ }
+ $data[] = clone($r);
+
+ }
+
+ $this->_rawData[$type] = $data;
+
+ }
+
+ private function _loadLibs() {
+
+ // include pChart pChart
+ require_once( $this->_baseChartPath . '/' . 'pChart' . '/' . 'pData.class.php');
+ require_once( $this->_baseChartPath . '/' . 'pChart' . '/' . 'pChart.class.php');
+ }
+
+ /**
+ * NUmerical sort function
+ *
+ * @param $first
+ * @param $second
+ * @return unknown_type
+ */
+ protected function _sortSourcesDataCompareFunction( $first, $second) {
+
+ // need config, to know which data user wants to display : visits, unique visitors, pageviews
+ $sefConfig = Sh404sefFactory::getConfig();
+ // Google does not allow combining dimension=ga:medium with metric = unique visitors
+ $metric = $sefConfig->analyticsDashboardDataType == 'ga:visitors' ?
+ 'ga:visits' : $sefConfig->analyticsDashboardDataType;
+
+ $dataTypeString = str_replace( 'ga:', '', $metric);
+
+ if ($first->$dataTypeString == $second->$dataTypeString) {
+ return 0;
+ }
+
+ return $first->$dataTypeString > $second->$dataTypeString ? +1 : -1;
+ }
+
+}
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportaliases.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportaliases.php
new file mode 100644
index 00000000..aed3ce69
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportaliases.php
@@ -0,0 +1,311 @@
+ array( 'task' => 'doTerminate', 'view' => 'wizard', 'layout' => 'default')
+ ,-1 => array( 'task' => 'doCancel', 'view' => 'wizard', 'layout' => 'default')
+ , 0 => array( 'task' => 'doStart', 'view' => 'wizard', 'layout' => 'default')
+ , 1 => array( 'task' => 'doExport', 'view' => 'wizard', 'layout' => 'default')
+ , 2 => array( 'task' => 'doDownload', 'view' => 'wizard', 'layout' => 'default')
+
+ );
+
+ public $_stepsCount = 0;
+ public $_steps = array( 'next' => 0, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+ public $_button = '';
+ public $_buttonsList = array ('next', 'previous', 'terminate', 'cancel');
+ // visible buttons are displayed as toolbar pressbutton
+ // buttons not on that list are passed as 'hidden' post data
+ public $_visibleButtonsList = array ( 'previous', 'next', 'terminate', 'cancel');
+
+ protected $_context = 'aliases';
+ protected $_total = 0;
+ protected $_parentController = null;
+ protected $_filename = '';
+
+ const MAX_PAGEIDS_PER_STEP = 100;
+
+ /**
+ * Constructor, keep reference to controller
+ * which called the adapter
+ * @param unknown_type $parentController
+ */
+ public function __construct( $parentController) {
+
+ $this->_parentController = $parentController;
+
+ }
+
+ /**
+ * Parameters for current adapter, to be used by parent controller
+ *
+ */
+ public function setup() {
+
+ $this->_stepsCount = count( $this->_stepsMap);
+
+ // prepare data for controller
+ $properties = array();
+
+ $properties['_defaultController'] = 'wizard';
+ $properties['_defaultTask'] = '';
+ $properties['_defaultModel'] = '';
+ $properties['_defaultView'] = 'wizard';
+ $properties['_defaultLayout'] = 'default';
+
+ $properties['_returnController'] = 'aliases';
+ $properties['_returnTask'] = '';
+ $properties['_returnView'] = 'aliases';
+ $properties['_returnLayout'] = 'default';
+ $properties['_pageTitle'] = JText::_('COM_SH404SEF_EXPORTING_TITLE');
+
+ return $properties;
+
+ }
+
+ /**
+ * First step, by default a message
+ * and a Terminate button
+ *
+ */
+ public function doStart() {
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('next', 'cancel');
+
+ // next steps definition
+ $this->_steps = array( 'next' => 1, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ // return results
+ $result = array();
+
+ $result['mainText'] = JText::_('COM_SH404SEF_EXPORT_ALIASES_START');
+
+ return $result;
+
+ }
+
+ /**
+ * Second step, export data
+ *
+ */
+ public function doExport() {
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ();
+
+ // A customiser :
+ $this->_steps = array( 'next' => 1, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ // return results
+ $result = array();
+
+ // exporting a limited set of pageids at a time
+ $nextStart = JRequest::getInt( 'nextstart', 0);
+
+ // are we adding to an existing data file ?
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // calculate number of items to export
+ $model = ShlMvcModel_Base::getInstance( 'aliases', 'Sh404sefModel');
+ $model->setContext( 'aliases.default');
+ $options = (object) array('layout' => 'default', 'includeHomeData' => true);
+ $this->_total = $model->getTotal( $options);
+
+ // do we have anything to export ?
+ if (empty( $this->_total)) {
+ $result['mainText'] = JText::_('COM_SH404SEF_NOTHING_TO_EXPORT');
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('terminate');
+ // next step so to trigger download, as file is ready now
+ $this->_steps = array( 'next' => -2, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ return $result;
+ }
+
+ // get new start item
+ if (empty( $nextStart)) {
+ // this is first pass, starting from 0
+ $start = 0;
+ $nextStart = $start + self::MAX_PAGEIDS_PER_STEP;
+ if ($nextStart >= $this->_total) {
+ // reached the end
+ $nextStart = $this->_total;
+ }
+ } else {
+ $start = $nextStart;
+ $nextStart = $start + self::MAX_PAGEIDS_PER_STEP;
+ }
+
+ // are we done ? if so, move to next step
+ $result['hiddenText'] = '';
+
+ if ($start >= $this->_total) {
+ $result['mainText'] = JText::sprintf('COM_SH404SEF_EXPORT_DONE', $this->_total);
+ $result['mainText'] .= $this->_getTerminateOptions();
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('terminate');
+ // next step so to trigger download, as file is ready now
+ $this->_steps = array( 'next' => 2, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+ } else {
+
+ // actually export items
+ $this->_export( $start);
+
+ // continuing for another round
+ $result['mainText'] = JText::sprintf('COM_SH404SEF_EXPORT_EXPORTING', $start + 1, $this->_total);
+ $result['hiddenText'] = '';
+ $result['nextStart'] = $nextStart;
+ }
+
+ $result['continue'] = array( 'task' => 'next', 'nextstart' => $nextStart, 'filename' => base64_encode($this->_filename));
+ $result['continue'] = array_merge( $result['continue'], $this->_steps);
+ $result['hiddenText'] .= '';
+
+ return $result;
+
+ }
+
+ /**
+ * Last step, send results as download
+ *
+ */
+ public function doDownload() {
+
+
+ // fake content
+ $data = '';
+
+ // get the current filename with path
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // get a more readable filename
+ $displayName = date('Y-m-d') . '_' . $this->_context . '_export.txt';
+
+ Sh404sefHelperFiles::triggerDownload( $this->_filename, $displayName);
+
+ }
+
+ /**
+ * Close the wizard window and redirect to default page
+ *
+ */
+ public function doTerminate() {
+
+ // are we set to purge temporary files ?
+ $purgeTempFiles = JRequest::getInt( 'purge_temp_files', 0);
+ if (!empty($purgeTempFiles)) {
+ Sh404sefHelperFiles::purgeTempFiles( 'sh404sef_export_' . $this->_context);
+ }
+
+ // now go back to main page
+ $result = array( 'redirectTo' => true);
+
+ return $result;
+
+ }
+
+ /**
+ * Close the wizard window and redirect to default page
+ *
+ */
+ public function doCancel() {
+
+ $result = array();
+ $result['redirectTo'] = true;
+ $result['redirectOptions'] = array( 'sh404sefMsg' => 'COM_SH404SEF_WIZARD_CANCELLED');
+
+ return $result;
+
+ }
+
+ protected function _export( $start) {
+
+ // do we have a valid filename
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // put some data in the file
+ $end = $start + self::MAX_PAGEIDS_PER_STEP + 1;
+ $end = $end > $this->_total ? $this->_total : $end;
+
+ // fetch pageIds record from model
+ $model = ShlMvcModel_Base::getInstance( 'aliases', 'Sh404sefModel');
+ $model->setContext( 'aliases.default');
+ $options = (object) array('layout' => 'default', 'includeHomeData' => true);
+ $records = $model->getList( $options, $returnZeroElement = false, $start, $forcedLimit = self::MAX_PAGEIDS_PER_STEP);
+
+ // do we need a header written to the file, for first record
+ $header = $start == 0 ? Sh404sefHelperGeneral::getExportHeaders( $this->_context) . "\n" : '';
+
+ // format them for text file output
+ $data = '';
+ $counter = $start;
+ $glue = Sh404sefHelperFiles::$stringDelimiter . Sh404sefHelperFiles::$fieldDelimiter . Sh404sefHelperFiles::$stringDelimiter;
+ if (!empty( $records)) {
+ foreach( $records as $record) {
+ $counter++;
+ if ($record->newurl == sh404SEF_HOMEPAGE_CODE) {
+ $record->newurl = '__ Homepage __';
+ }
+ $textRecord = $record->alias . $glue . $record->oldurl . $glue . $record->newurl
+ . $glue . $record->type
+ . $glue . $record->hits . Sh404sefHelperFiles::$stringDelimiter
+ ;
+ $line = Sh404sefHelperFiles::$stringDelimiter . $counter . $glue . $textRecord;
+ $data .= $line . "\n";
+ }
+ }
+
+ // prepare data for storage
+ if (!empty( $header)) {
+ // first record written to file, prepend header
+ $data = $header . $data;
+ }
+
+ // store in file
+ $status = Sh404sefHelperFiles::appendToFile( $this->_filename, $data);
+
+ // return any error
+ return $status;
+
+ }
+
+ protected function _getTerminateOptions() {
+
+ $options = '
';
+ $options .= '';
+ $options .= JText::_('COM_SH404SEF_PURGE_TEMP_FILES');
+ $options .= ' ';
+
+ return $options;
+ }
+
+}
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportmetas.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportmetas.php
new file mode 100644
index 00000000..9354c2dc
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportmetas.php
@@ -0,0 +1,364 @@
+ array( 'task' => 'doTerminate', 'view' => 'wizard', 'layout' => 'default')
+ ,-1 => array( 'task' => 'doCancel', 'view' => 'wizard', 'layout' => 'default')
+ , 0 => array( 'task' => 'doStart', 'view' => 'wizard', 'layout' => 'default')
+ , 1 => array( 'task' => 'doExport', 'view' => 'wizard', 'layout' => 'default')
+ , 2 => array( 'task' => 'doDownload', 'view' => 'wizard', 'layout' => 'default')
+
+ );
+
+ public $_stepsCount = 0;
+ public $_steps = array( 'next' => 0, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+ public $_button = '';
+ public $_buttonsList = array ('next', 'previous', 'terminate', 'cancel');
+ // visible buttons are displayed as toolbar pressbutton
+ // buttons not on that list are passed as 'hidden' post data
+ public $_visibleButtonsList = array ( 'previous', 'next', 'terminate', 'cancel');
+
+
+ protected $_context = 'metas';
+ protected $_total = 0;
+ protected $_parentController = null;
+ protected $_filename = '';
+
+ const MAX_PAGEIDS_PER_STEP = 100;
+
+ /**
+ * Constructor, keep reference to controller
+ * which called the adapter
+ * @param unknown_type $parentController
+ */
+ public function __construct( $parentController) {
+
+ $this->_parentController = $parentController;
+
+ }
+
+ /**
+ * Parameters for current adapter, to be used by parent controller
+ *
+ */
+ public function setup() {
+
+ $this->_stepsCount = count( $this->_stepsMap);
+
+ // prepare data for controller
+ $properties = array();
+
+ $properties['_defaultController'] = 'wizard';
+ $properties['_defaultTask'] = '';
+ $properties['_defaultModel'] = '';
+ $properties['_defaultView'] = 'wizard';
+ $properties['_defaultLayout'] = 'default';
+
+ $properties['_returnController'] = 'metas';
+ $properties['_returnTask'] = '';
+ $properties['_returnView'] = 'metas';
+ $properties['_returnLayout'] = 'default';
+ $properties['_pageTitle'] = JText::_('COM_SH404SEF_EXPORTING_TITLE');
+
+ return $properties;
+
+ }
+
+ /**
+ * First step, by default a message
+ * and a Terminate button
+ *
+ */
+ public function doStart() {
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('next', 'cancel');
+
+ // A customiser :
+ $this->_steps = array( 'next' => 1, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ // return results
+ $result = array();
+
+ $result['mainText'] = JText::_('COM_SH404SEF_EXPORT_METAS_START');
+
+ return $result;
+
+ }
+
+ /**
+ * Second step, export data
+ *
+ */
+ public function doExport() {
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ();
+
+ // next steps definition
+ $this->_steps = array( 'next' => 1, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ // return results
+ $result = array();
+
+ // exporting a limited set of pageids at a time
+ $nextStart = JRequest::getInt( 'nextstart', 0);
+
+ // are we adding to an existing data file ?
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // calculate number of items to export
+ $model = ShlMvcModel_Base::getInstance( 'urls', 'Sh404sefModel');
+ $model->setContext( 'metas.default');
+ $options = (object) array('layout' => 'export', 'getMetaData' => true, 'onlyWithMetaData' => true, 'simpleUrlList' => true);
+ $this->_total = $model->getTotal( $options);
+
+ // do we have anything to export ?
+ if (empty( $this->_total)) {
+ $result['mainText'] = JText::_('COM_SH404SEF_NOTHING_TO_EXPORT');
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('terminate');
+ // next step so to trigger download, as file is ready now
+ $this->_steps = array( 'next' => -2, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ return $result;
+ }
+
+ // get new start item
+ if (empty( $nextStart)) {
+ // this is first pass, starting from 0
+ $start = 0;
+ $nextStart = $start + self::MAX_PAGEIDS_PER_STEP;
+ if ($nextStart >= $this->_total) {
+ // reached the end
+ $nextStart = $this->_total;
+ }
+ } else {
+ $start = $nextStart;
+ $nextStart = $start + self::MAX_PAGEIDS_PER_STEP;
+ }
+
+ // are we done ? if so, move to next step
+ $result['hiddenText'] = '';
+
+ if ($start >= $this->_total) {
+ // append homepage data. Hard to put them at the beginning, that'd make the whole counting logic more complex
+ $this->_exportHomePageData( $this->_total);
+
+ // keep going to terminate page
+ $result['mainText'] = JText::sprintf('COM_SH404SEF_EXPORT_DONE', $this->_total);
+ $result['mainText'] .= $this->_getTerminateOptions();
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('terminate');
+ // next step so to trigger download, as file is ready now
+ $this->_steps = array( 'next' => 2, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+ } else {
+
+ // actually export items
+ $this->_export( $start);
+
+ // continuing for another round
+ $result['mainText'] = JText::sprintf('COM_SH404SEF_EXPORT_EXPORTING', $start + 1, $this->_total);
+ $result['hiddenText'] = '';
+ $result['nextStart'] = $nextStart;
+ }
+
+ $result['continue'] = array( 'task' => 'next', 'nextstart' => $nextStart, 'filename' => base64_encode($this->_filename));
+ $result['continue'] = array_merge( $result['continue'], $this->_steps);
+ $result['hiddenText'] .= '';
+
+ return $result;
+
+ }
+
+ /**
+ * Last step, send results as download
+ *
+ */
+ public function doDownload() {
+
+
+ // fake content
+ $data = '';
+
+ // get the current filename with path
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // get a more readable filename
+ $displayName = date('Y-m-d') . '_' . $this->_context . '_export.txt';
+
+ Sh404sefHelperFiles::triggerDownload( $this->_filename, $displayName);
+
+ }
+
+ /**
+ * Close the wizard window and redirect to default page
+ *
+ */
+ public function doTerminate() {
+
+ // are we set to purge temporary files ?
+ $purgeTempFiles = JRequest::getInt( 'purge_temp_files', 0);
+ if (!empty($purgeTempFiles)) {
+ Sh404sefHelperFiles::purgeTempFiles( 'sh404sef_export_' . $this->_context);
+ }
+
+ // now go back to main page
+ $result = array( 'redirectTo' => true);
+
+ return $result;
+
+ }
+
+ /**
+ * Close the wizard window and redirect to default page
+ *
+ */
+ public function doCancel() {
+
+ $result = array();
+ $result['redirectTo'] = true;
+ $result['redirectOptions'] = array( 'sh404sefMsg' => 'COM_SH404SEF_WIZARD_CANCELLED');
+
+ return $result;
+
+ }
+
+ protected function _export( $start) {
+
+ // do we have a valid filename
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // put some data in the file
+ $end = $start + self::MAX_PAGEIDS_PER_STEP + 1;
+ $end = $end > $this->_total ? $this->_total : $end;
+
+ // fetch pageIds record from model
+ $model = ShlMvcModel_Base::getInstance( 'urls', 'Sh404sefModel');
+ $model->setContext( 'metas.default');
+ $options = (object) array('layout' => 'export', 'getMetaData' => true, 'onlyWithMetaData' => true, 'simpleUrlList' => true);
+ $records = $model->getList( $options, $returnZeroElement = false, $start, $forcedLimit = self::MAX_PAGEIDS_PER_STEP);
+
+ // do we need a header written to the file, for first record
+ $header = $start == 0 ? Sh404sefHelperGeneral::getExportHeaders( $this->_context) . "\n" : '';
+
+ // format them for text file output
+ $data = '';
+ $counter = $start;
+ $glue = Sh404sefHelperFiles::$stringDelimiter . Sh404sefHelperFiles::$fieldDelimiter . Sh404sefHelperFiles::$stringDelimiter;
+ if (!empty( $records)) {
+ foreach( $records as $record) {
+ $counter++;
+ $data .= $this->_createLine( $record, $counter, $glue);
+ }
+ }
+
+ // prepare data for storage
+ if (!empty( $header)) {
+ // first record written to file, prepend header
+ $data = $header . $data;
+ }
+
+ // store in file
+ $status = Sh404sefHelperFiles::appendToFile( $this->_filename, $data);
+
+ // return any error
+ return $status;
+
+ }
+
+ protected function _exportHomePageData( $start) {
+
+ // get data from model
+ $model = ShlMvcModel_Base::getInstance( 'metas', 'Sh404sefModel');
+ $model->setContext( 'metas.default');
+ $options = (object) array('layout' => 'default', 'newurl' => sh404SEF_HOMEPAGE_CODE);
+ $this->_total += 1;
+ $homePageMetaData = $model->getList( $options);
+
+ // format them for text file output
+ $data = '';
+ $counter = $start;
+ $glue = Sh404sefHelperFiles::$stringDelimiter . Sh404sefHelperFiles::$fieldDelimiter . Sh404sefHelperFiles::$stringDelimiter;
+ if (!empty( $homePageMetaData)) {
+ foreach( $homePageMetaData as $record) {
+ $counter++;
+ $record->oldurl = '';
+ $record->newurl = '__ Homepage __';
+ $data .= $this->_createLine( $record, $counter, $glue);
+ }
+ }
+
+ // prepare data for storage
+ if (!empty( $header)) {
+ // first record written to file, prepend header
+ $data = $header . $data;
+ }
+
+ // store in file
+ $status = Sh404sefHelperFiles::appendToFile( $this->_filename, $data);
+
+ }
+
+ /**
+ * Creates an export file line, based on db record
+ *
+ * @param $record the data coming from DB
+ * @param $counter, running counter
+ * @param $glue , glue string between elements of records
+ */
+ protected function _createLine( $record, $counter, $glue) {
+
+ $textRecord = $record->oldurl . $glue . $record->newurl
+ . $glue . (empty( $record->cpt) ? 0 : $record->cpt)
+ . $glue . (empty( $record->rank) ? 0 : $record->rank)
+ . $glue . (!isset( $record->dateadd) ? '0000-00-00' : $record->dateadd)
+ . $glue . Sh404sefHelperFiles::csvQuote( $record->metatitle)
+ . $glue . Sh404sefHelperFiles::csvQuote( $record->metadesc)
+ . $glue . Sh404sefHelperFiles::csvQuote( $record->metakey)
+ . $glue . Sh404sefHelperFiles::csvQuote( $record->metalang)
+ . $glue . Sh404sefHelperFiles::csvQuote( $record->metarobots) . Sh404sefHelperFiles::$stringDelimiter;
+ $line = Sh404sefHelperFiles::$stringDelimiter . $counter . $glue . $textRecord . "\n";
+
+ return $line;
+ }
+
+ protected function _getTerminateOptions() {
+
+ $options = '
';
+ $options .= '';
+ $options .= JText::_('COM_SH404SEF_PURGE_TEMP_FILES');
+ $options .= ' ';
+
+ return $options;
+ }
+
+}
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportpageids.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportpageids.php
new file mode 100644
index 00000000..5cae43cd
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportpageids.php
@@ -0,0 +1,308 @@
+ array( 'task' => 'doTerminate', 'view' => 'wizard', 'layout' => 'default')
+ ,-1 => array( 'task' => 'doCancel', 'view' => 'wizard', 'layout' => 'default')
+ , 0 => array( 'task' => 'doStart', 'view' => 'wizard', 'layout' => 'default')
+ , 1 => array( 'task' => 'doExport', 'view' => 'wizard', 'layout' => 'default')
+ , 2 => array( 'task' => 'doDownload', 'view' => 'wizard', 'layout' => 'default')
+
+ );
+
+ public $_stepsCount = 0;
+ public $_steps = array( 'next' => 0, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+ public $_button = '';
+ public $_buttonsList = array ('next', 'previous', 'terminate', 'cancel');
+ // visible buttons are displayed as toolbar pressbutton
+ // buttons not on that list are passed as 'hidden' post data
+ public $_visibleButtonsList = array ( 'previous', 'next', 'terminate', 'cancel');
+
+ protected $_context = 'pageids';
+ protected $_total = 0;
+ protected $_parentController = null;
+ protected $_filename = '';
+
+ const MAX_PAGEIDS_PER_STEP = 100;
+
+ /**
+ * Constructor, keep reference to controller
+ * which called the adapter
+ * @param unknown_type $parentController
+ */
+ public function __construct( $parentController) {
+
+ $this->_parentController = $parentController;
+
+ }
+
+ /**
+ * Parameters for current adapter, to be used by parent controller
+ *
+ */
+ public function setup() {
+
+ $this->_stepsCount = count( $this->_stepsMap);
+
+ // prepare data for controller
+ $properties = array();
+
+ $properties['_defaultController'] = 'wizard';
+ $properties['_defaultTask'] = '';
+ $properties['_defaultModel'] = '';
+ $properties['_defaultView'] = 'wizard';
+ $properties['_defaultLayout'] = 'default';
+
+ $properties['_returnController'] = 'pageids';
+ $properties['_returnTask'] = '';
+ $properties['_returnView'] = 'pageids';
+ $properties['_returnLayout'] = 'default';
+ $properties['_pageTitle'] = JText::_('COM_SH404SEF_EXPORTING_TITLE');
+
+ return $properties;
+
+ }
+
+ /**
+ * First step, by default a message
+ * and a Terminate button
+ *
+ */
+ public function doStart() {
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('next', 'cancel');
+
+ // next steps definition
+ $this->_steps = array( 'next' => 1, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ // return results
+ $result = array();
+
+ $result['mainText'] = JText::_('COM_SH404SEF_EXPORT_PAGEIDS_START');
+
+ return $result;
+
+ }
+
+ /**
+ * Second step, export data
+ *
+ */
+ public function doExport() {
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ();
+
+ // next steps definition
+ $this->_steps = array( 'next' => 1, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ // return results
+ $result = array();
+
+ // exporting a limited set of pageids at a time
+ $nextStart = JRequest::getInt( 'nextstart', 0);
+
+ // are we adding to an existing data file ?
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // calculate number of items to export
+ $model = ShlMvcModel_Base::getInstance( 'urls', 'Sh404sefModel');
+ $model->setContext( 'pageids.default');
+ $options = (object) array('layout' => 'default', 'getPageId' => true, 'simpleUrlList' => true,);
+ $this->_total = $model->getTotal( $options);
+
+ // do we have anything to export ?
+ if (empty( $this->_total)) {
+ $result['mainText'] = JText::_('COM_SH404SEF_NOTHING_TO_EXPORT');
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('terminate');
+ // next step so to trigger download, as file is ready now
+ $this->_steps = array( 'next' => -2, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ return $result;
+ }
+
+ // get new start item
+ if (empty( $nextStart)) {
+ // this is first pass, starting from 0
+ $start = 0;
+ $nextStart = $start + self::MAX_PAGEIDS_PER_STEP;
+ if ($nextStart >= $this->_total) {
+ // reached the end
+ $nextStart = $this->_total;
+ }
+ } else {
+ $start = $nextStart;
+ $nextStart = $start + self::MAX_PAGEIDS_PER_STEP;
+ }
+
+ // are we done ? if so, move to next step
+ $result['hiddenText'] = '';
+
+ if ($start >= $this->_total) {
+ $result['mainText'] = JText::sprintf('COM_SH404SEF_EXPORT_DONE', $this->_total);
+ $result['mainText'] .= $this->_getTerminateOptions();
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('terminate');
+ // next step so to trigger download, as file is ready now
+ $this->_steps = array( 'next' => 2, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+ } else {
+
+ // actually export items
+ $this->_export( $start);
+
+ // continuing for another round
+ $result['mainText'] = JText::sprintf('COM_SH404SEF_EXPORT_EXPORTING', $start + 1, $this->_total);
+ $result['hiddenText'] = '';
+ $result['nextStart'] = $nextStart;
+ }
+
+ $result['continue'] = array( 'task' => 'next', 'nextstart' => $nextStart, 'filename' => base64_encode($this->_filename));
+ $result['continue'] = array_merge( $result['continue'], $this->_steps);
+ $result['hiddenText'] .= '';
+
+ return $result;
+
+ }
+
+ /**
+ * Last step, send results as download
+ *
+ */
+ public function doDownload() {
+
+
+ // fake content
+ $data = '';
+
+ // get the current filename with path
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // get a more readable filename
+ $displayName = date('Y-m-d') . '_' . $this->_context . '_export.txt';
+
+ Sh404sefHelperFiles::triggerDownload( $this->_filename, $displayName);
+
+ }
+
+ /**
+ * Close the wizard window and redirect to default page
+ *
+ */
+ public function doTerminate() {
+
+ // are we set to purge temporary files ?
+ $purgeTempFiles = JRequest::getInt( 'purge_temp_files', 0);
+ if (!empty($purgeTempFiles)) {
+ Sh404sefHelperFiles::purgeTempFiles( 'sh404sef_export_' . $this->_context);
+ }
+
+ // now go back to main page
+ $result = array( 'redirectTo' => true);
+
+ return $result;
+
+ }
+
+ /**
+ * Close the wizard window and redirect to default page
+ *
+ */
+ public function doCancel() {
+
+ $result = array();
+ $result['redirectTo'] = true;
+ $result['redirectOptions'] = array( 'sh404sefMsg' => 'COM_SH404SEF_WIZARD_CANCELLED');
+
+ return $result;
+
+ }
+
+ protected function _export( $start) {
+
+ // do we have a valid filename
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // put some data in the file
+ $end = $start + self::MAX_PAGEIDS_PER_STEP + 1;
+ $end = $end > $this->_total ? $this->_total : $end;
+
+ // fetch pageIds record from model
+ $model = ShlMvcModel_Base::getInstance( 'urls', 'Sh404sefModel');
+ $model->setContext( 'pageids.default');
+ $options = (object) array('layout' => 'default', 'getPageId' => true, 'simpleUrlList' => true);
+ $records = $model->getList( $options, $returnZeroElement = false, $start, $forcedLimit = self::MAX_PAGEIDS_PER_STEP);
+
+ // do we need a header written to the file, for first record
+ $header = $start == 0 ? Sh404sefHelperGeneral::getExportHeaders( $this->_context) . "\n" : '';
+
+ // format them for text file output
+ $data = '';
+ $counter = $start;
+ $glue = Sh404sefHelperFiles::$stringDelimiter . Sh404sefHelperFiles::$fieldDelimiter . Sh404sefHelperFiles::$stringDelimiter;
+ if (!empty( $records)) {
+ foreach( $records as $record) {
+ $counter++;
+ $textRecord = $record->pageid . $glue . $record->oldurl . $glue . $record->nonsefurl
+ . $glue . $record->pageidtype
+ . $glue . $record->pageidhits . Sh404sefHelperFiles::$stringDelimiter
+ ;
+ $line = Sh404sefHelperFiles::$stringDelimiter . $counter . $glue . $textRecord;
+ $data .= $line . "\n";
+ }
+ }
+
+ // prepare data for storage
+ if (!empty( $header)) {
+ // first record written to file, prepend header
+ $data = $header . $data;
+ }
+
+ // store in file
+ $status = Sh404sefHelperFiles::appendToFile( $this->_filename, $data);
+
+ // return any error
+ return $status;
+
+ }
+
+ protected function _getTerminateOptions() {
+
+ $options = '
';
+ $options .= '';
+ $options .= JText::_('COM_SH404SEF_PURGE_TEMP_FILES');
+ $options .= ' ';
+
+ return $options;
+ }
+}
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exporturls.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exporturls.php
new file mode 100644
index 00000000..b12904ec
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exporturls.php
@@ -0,0 +1,317 @@
+ array( 'task' => 'doTerminate', 'view' => 'wizard', 'layout' => 'default')
+ ,-1 => array( 'task' => 'doCancel', 'view' => 'wizard', 'layout' => 'default')
+ , 0 => array( 'task' => 'doStart', 'view' => 'wizard', 'layout' => 'default')
+ , 1 => array( 'task' => 'doExport', 'view' => 'wizard', 'layout' => 'default')
+ , 2 => array( 'task' => 'doDownload', 'view' => 'wizard', 'layout' => 'default')
+
+ );
+
+ public $_stepsCount = 0;
+ public $_steps = array( 'next' => 0, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+ public $_button = '';
+ public $_buttonsList = array ('next', 'previous', 'terminate', 'cancel');
+ // visible buttons are displayed as toolbar pressbutton
+ // buttons not on that list are passed as 'hidden' post data
+ public $_visibleButtonsList = array ( 'previous', 'next', 'terminate', 'cancel');
+
+
+ protected $_context = 'urls';
+ protected $_total = 0;
+ protected $_parentController = null;
+ protected $_filename = '';
+
+ const MAX_PAGEIDS_PER_STEP = 100;
+
+ /**
+ * Constructor, keep reference to controller
+ * which called the adapter
+ * @param unknown_type $parentController
+ */
+ public function __construct( $parentController) {
+
+ $this->_parentController = $parentController;
+
+ }
+
+ /**
+ * Parameters for current adapter, to be used by parent controller
+ *
+ */
+ public function setup() {
+
+ $this->_stepsCount = count( $this->_stepsMap);
+
+ // prepare data for controller
+ $properties = array();
+
+ $properties['_defaultController'] = 'wizard';
+ $properties['_defaultTask'] = '';
+ $properties['_defaultModel'] = '';
+ $properties['_defaultView'] = 'wizard';
+ $properties['_defaultLayout'] = 'default';
+
+ $properties['_returnController'] = 'urls';
+ $properties['_returnTask'] = '';
+ $properties['_returnView'] = 'urls';
+ $properties['_returnLayout'] = 'default';
+ $properties['_pageTitle'] = JText::_('COM_SH404SEF_EXPORTING_TITLE');
+
+ return $properties;
+
+ }
+
+ /**
+ * First step, by default a message
+ * and a Terminate button
+ *
+ */
+ public function doStart() {
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('next', 'cancel');
+
+ // A customiser :
+ $this->_steps = array( 'next' => 1, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ // return results
+ $result = array();
+
+ $result['mainText'] = JText::_('COM_SH404SEF_EXPORT_URLS_START');
+
+ return $result;
+
+ }
+
+ /**
+ * Second step, export data
+ *
+ */
+ public function doExport() {
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ();
+
+ // next steps definition
+ $this->_steps = array( 'next' => 1, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ // return results
+ $result = array();
+
+ // exporting a limited set of pageids at a time
+ $nextStart = JRequest::getInt( 'nextstart', 0);
+
+ // are we adding to an existing data file ?
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // calculate number of items to export
+ $model = ShlMvcModel_Base::getInstance( 'urls', 'Sh404sefModel');
+ $model->setContext( 'urls.default');
+ $options = (object) array('layout' => 'export', 'getMetaData' => true, 'simpleUrlList' => true);
+ $this->_total = $model->getTotal( $options);
+
+ // do we have anything to export ?
+ if (empty( $this->_total)) {
+ $result['mainText'] = JText::_('COM_SH404SEF_NOTHING_TO_EXPORT');
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('terminate');
+ // next step so to trigger download, as file is ready now
+ $this->_steps = array( 'next' => -2, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ return $result;
+ }
+
+ // get new start item
+ if (empty( $nextStart)) {
+ // this is first pass, starting from 0
+ $start = 0;
+ $nextStart = $start + self::MAX_PAGEIDS_PER_STEP;
+ if ($nextStart >= $this->_total) {
+ // reached the end
+ $nextStart = $this->_total;
+ }
+ } else {
+ $start = $nextStart;
+ $nextStart = $start + self::MAX_PAGEIDS_PER_STEP;
+ }
+
+ // are we done ? if so, move to next step
+ $result['hiddenText'] = '';
+
+ if ($start >= $this->_total) {
+ $result['mainText'] = JText::sprintf('COM_SH404SEF_EXPORT_DONE', $this->_total);
+ $result['mainText'] .= $this->_getTerminateOptions();
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('terminate');
+ // next step so to trigger download, as file is ready now
+ $this->_steps = array( 'next' => 2, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+ } else {
+
+ // actually export items
+ $this->_export( $start);
+
+ // continuing for another round
+ $result['mainText'] = JText::sprintf('COM_SH404SEF_EXPORT_EXPORTING', $start + 1, $this->_total);
+ $result['hiddenText'] = '';
+ $result['nextStart'] = $nextStart;
+ }
+
+ $result['continue'] = array( 'task' => 'next', 'nextstart' => $nextStart, 'filename' => base64_encode($this->_filename));
+ $result['continue'] = array_merge( $result['continue'], $this->_steps);
+ $result['hiddenText'] .= '';
+
+ return $result;
+
+ }
+
+ /**
+ * Last step, send results as download
+ *
+ */
+ public function doDownload() {
+
+
+ // fake content
+ $data = '';
+
+ // get the current filename with path
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // get a more readable filename
+ $displayName = date('Y-m-d') . '_' . $this->_context . '_export.txt';
+
+ Sh404sefHelperFiles::triggerDownload( $this->_filename, $displayName);
+
+ }
+
+ /**
+ * Close the wizard window and redirect to default page
+ *
+ */
+ public function doTerminate() {
+
+ // are we set to purge temporary files ?
+ $purgeTempFiles = JRequest::getInt( 'purge_temp_files', 0);
+ if (!empty($purgeTempFiles)) {
+ Sh404sefHelperFiles::purgeTempFiles( 'sh404sef_export_' . $this->_context);
+ }
+
+ // now go back to main page
+ $result = array( 'redirectTo' => true);
+
+ return $result;
+
+ }
+
+ /**
+ * Close the wizard window and redirect to default page
+ *
+ */
+ public function doCancel() {
+
+ $result = array();
+ $result['redirectTo'] = true;
+ $result['redirectOptions'] = array( 'sh404sefMsg' => 'COM_SH404SEF_WIZARD_CANCELLED');
+
+ return $result;
+
+ }
+
+ protected function _export( $start) {
+
+ // do we have a valid filename
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // put some data in the file
+ $end = $start + self::MAX_PAGEIDS_PER_STEP + 1;
+ $end = $end > $this->_total ? $this->_total : $end;
+
+ // fetch pageIds record from model
+ $model = ShlMvcModel_Base::getInstance( 'urls', 'Sh404sefModel');
+ $model->setContext( 'urls.default');
+ $options = (object) array('layout' => 'export', 'getMetaData' => true, 'getPageId' => false, 'simpleUrlList' => true);
+ $records = $model->getList( $options, $returnZeroElement = false, $start, $forcedLimit = self::MAX_PAGEIDS_PER_STEP);
+
+ // do we need a header written to the file, for first record
+ $header = $start == 0 ? Sh404sefHelperGeneral::getExportHeaders( $this->_context) . "\n" : '';
+
+ // format them for text file output
+ $data = '';
+ $counter = $start;
+ $glue = Sh404sefHelperFiles::$stringDelimiter . Sh404sefHelperFiles::$fieldDelimiter . Sh404sefHelperFiles::$stringDelimiter;
+ if (!empty( $records)) {
+ foreach( $records as $record) {
+ $counter++;
+ $textRecord = $record->oldurl . $glue . $record->newurl
+ . $glue . $record->cpt
+ . $glue . $record->rank
+ . $glue . $record->dateadd
+ . $glue . Sh404sefHelperFiles::csvQuote( $record->metatitle)
+ . $glue . Sh404sefHelperFiles::csvQuote( $record->metadesc)
+ . $glue . Sh404sefHelperFiles::csvQuote( $record->metakey)
+ . $glue . Sh404sefHelperFiles::csvQuote( $record->metalang)
+ . $glue . Sh404sefHelperFiles::csvQuote( $record->metarobots)
+ . $glue . Sh404sefHelperFiles::csvQuote( $record->canonical)
+ ;
+ $line = Sh404sefHelperFiles::$stringDelimiter . $counter . $glue . $textRecord . Sh404sefHelperFiles::$stringDelimiter;
+ $data .= $line . "\n";
+ }
+ }
+
+ // prepare data for storage
+ if (!empty( $header)) {
+ // first record written to file, prepend header
+ $data = $header . $data;
+ }
+
+ // store in file
+ $status = Sh404sefHelperFiles::appendToFile( $this->_filename, $data);
+
+ // return any error
+ return $status;
+
+ }
+
+ protected function _getTerminateOptions() {
+
+ $options = '
';
+ $options .= '';
+ $options .= JText::_('COM_SH404SEF_PURGE_TEMP_FILES');
+ $options .= ' ';
+
+ return $options;
+ }
+
+}
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportview404.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportview404.php
new file mode 100644
index 00000000..333d98ef
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/exportview404.php
@@ -0,0 +1,315 @@
+ array( 'task' => 'doTerminate', 'view' => 'wizard', 'layout' => 'default')
+ ,-1 => array( 'task' => 'doCancel', 'view' => 'wizard', 'layout' => 'default')
+ , 0 => array( 'task' => 'doStart', 'view' => 'wizard', 'layout' => 'default')
+ , 1 => array( 'task' => 'doExport', 'view' => 'wizard', 'layout' => 'default')
+ , 2 => array( 'task' => 'doDownload', 'view' => 'wizard', 'layout' => 'default')
+
+ );
+
+ public $_stepsCount = 0;
+ public $_steps = array( 'next' => 0, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+ public $_button = '';
+ public $_buttonsList = array ('next', 'previous', 'terminate', 'cancel');
+ // visible buttons are displayed as toolbar pressbutton
+ // buttons not on that list are passed as 'hidden' post data
+ public $_visibleButtonsList = array ( 'previous', 'next', 'terminate', 'cancel');
+
+
+ protected $_context = 'view404';
+ protected $_total = 0;
+ protected $_parentController = null;
+ protected $_filename = '';
+
+ const MAX_PAGEIDS_PER_STEP = 100;
+
+ /**
+ * Constructor, keep reference to controller
+ * which called the adapter
+ * @param unknown_type $parentController
+ */
+ public function __construct( $parentController) {
+
+ $this->_parentController = $parentController;
+
+ }
+
+ /**
+ * Parameters for current adapter, to be used by parent controller
+ *
+ */
+ public function setup() {
+
+ $this->_stepsCount = count( $this->_stepsMap);
+
+ // prepare data for controller
+ $properties = array();
+
+ $properties['_defaultController'] = 'wizard';
+ $properties['_defaultTask'] = '';
+ $properties['_defaultModel'] = '';
+ $properties['_defaultView'] = 'wizard';
+ $properties['_defaultLayout'] = 'default';
+
+ $properties['_returnController'] = 'urls';
+ $properties['_returnTask'] = '';
+ $properties['_returnView'] = 'urls';
+ $properties['_returnLayout'] = 'view404';
+ $properties['_pageTitle'] = JText::_('COM_SH404SEF_EXPORTING_TITLE');
+
+ return $properties;
+
+ }
+
+ /**
+ * First step, by default a message
+ * and a Terminate button
+ *
+ */
+ public function doStart() {
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('next', 'cancel');
+
+ // A customiser :
+ $this->_steps = array( 'next' => 1, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ // return results
+ $result = array();
+
+ $result['mainText'] = JText::_('COM_SH404SEF_EXPORT_VIEW404_START');
+
+ return $result;
+
+ }
+
+ /**
+ * Second step, export data
+ *
+ */
+ public function doExport() {
+
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ();
+
+ // next steps definition
+ $this->_steps = array( 'next' => 1, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ // return results
+ $result = array();
+
+ // exporting a limited set of pageids at a time
+ $nextStart = JRequest::getInt( 'nextstart', 0);
+
+ // are we adding to an existing data file ?
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // calculate number of items to export
+ $model = ShlMvcModel_Base::getInstance( 'urls', 'Sh404sefModel');
+ $model->setContext( 'urls.view404');
+ $options = (object) array('layout' => 'view404');
+ $this->_total = $model->getTotal( $options);
+
+ // do we have anything to export ?
+ if (empty( $this->_total)) {
+ $result['mainText'] = JText::_('COM_SH404SEF_NOTHING_TO_EXPORT');
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('terminate');
+ // next step so to trigger download, as file is ready now
+ $this->_steps = array( 'next' => -2, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+
+ return $result;
+ }
+
+ // get new start item
+ if (empty( $nextStart)) {
+ // this is first pass, starting from 0
+ $start = 0;
+ $nextStart = $start + self::MAX_PAGEIDS_PER_STEP;
+ if ($nextStart >= $this->_total) {
+ // reached the end
+ $nextStart = $this->_total;
+ }
+ } else {
+ $start = $nextStart;
+ $nextStart = $start + self::MAX_PAGEIDS_PER_STEP;
+ }
+
+ // are we done ? if so, move to next step
+ $result['hiddenText'] = '';
+
+ if ($start >= $this->_total) {
+ $result['mainText'] = JText::sprintf('COM_SH404SEF_EXPORT_DONE', $this->_total);
+ $result['mainText'] .= $this->_getTerminateOptions();
+ // which button should be displayed ?
+ $this->_visibleButtonsList = array ('terminate');
+ // next step so to trigger download, as file is ready now
+ $this->_steps = array( 'next' => 2, 'previous' => 0, 'cancel' => -1, 'terminate' => -2);
+ } else {
+
+ // actually export items
+ $this->_export( $start);
+
+ // continuing for another round
+ $result['mainText'] = JText::sprintf('COM_SH404SEF_EXPORT_EXPORTING', $start + 1, $this->_total);
+ $result['hiddenText'] = '';
+ $result['nextStart'] = $nextStart;
+ }
+
+ $result['continue'] = array( 'task' => 'next', 'nextstart' => $nextStart, 'filename' => base64_encode($this->_filename));
+ $result['continue'] = array_merge( $result['continue'], $this->_steps);
+ $result['hiddenText'] .= '';
+
+ return $result;
+
+ }
+
+ /**
+ * Last step, send results as download
+ *
+ */
+ public function doDownload() {
+
+
+ // fake content
+ $data = '';
+
+ // get the current filename with path
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // get a more readable filename
+ $displayName = date('Y-m-d') . '_' . $this->_context . '_export.txt';
+
+ Sh404sefHelperFiles::triggerDownload( $this->_filename, $displayName);
+
+ }
+
+ /**
+ * Close the wizard window and redirect to default page
+ *
+ */
+ public function doTerminate() {
+
+ // are we set to purge temporary files ?
+ $purgeTempFiles = JRequest::getInt( 'purge_temp_files', 0);
+ if (!empty($purgeTempFiles)) {
+ Sh404sefHelperFiles::purgeTempFiles( 'sh404sef_export_' . $this->_context);
+ }
+
+ // now go back to main page
+ $result = array( 'redirectTo' => true);
+
+ return $result;
+
+ }
+
+ /**
+ * Close the wizard window and redirect to default page
+ *
+ */
+ public function doCancel() {
+
+ $result = array();
+ $result['redirectTo'] = true;
+ $result['redirectOptions'] = array( 'sh404sefMsg' => 'COM_SH404SEF_WIZARD_CANCELLED');
+
+ return $result;
+
+ }
+
+ protected function _export( $start) {
+
+ // do we have a valid filename
+ $this->_filename = Sh404sefHelperFiles::createFileName( $this->_filename, 'sh404sef_export_' . $this->_context);
+
+ // put some data in the file
+ $end = $start + self::MAX_PAGEIDS_PER_STEP + 1;
+ $end = $end > $this->_total ? $this->_total : $end;
+
+ // fetch pageIds record from model
+ $model = ShlMvcModel_Base::getInstance( 'urls', 'Sh404sefModel');
+ $model->setContext( 'urls.view404');
+ $options = (object) array('layout' => 'view404');
+ $records = $model->getList( $options, $returnZeroElement = false, $start, $forcedLimit = self::MAX_PAGEIDS_PER_STEP);
+
+ // do we need a header written to the file, for first record
+ $header = $start == 0 ? Sh404sefHelperGeneral::getExportHeaders( $this->_context) . "\n" : '';
+
+ // format them for text file output
+ $data = '';
+ $counter = $start;
+ $glue = Sh404sefHelperFiles::$stringDelimiter . Sh404sefHelperFiles::$fieldDelimiter . Sh404sefHelperFiles::$stringDelimiter;
+ if (!empty( $records)) {
+ foreach( $records as $record) {
+ $counter++;
+ $textRecord = $record->oldurl . $glue . $record->newurl
+ . $glue . $record->cpt
+ . $glue . $record->rank
+ . $glue . $record->dateadd
+ . $glue . ''//Sh404sefHelperFiles::csvQuote( $record->metatitle)
+ . $glue . ''//Sh404sefHelperFiles::csvQuote( $record->metadesc)
+ . $glue . ''//Sh404sefHelperFiles::csvQuote( $record->metakey)
+ . $glue . ''//Sh404sefHelperFiles::csvQuote( $record->metalang)
+ . $glue . ''//Sh404sefHelperFiles::csvQuote( $record->metarobots)
+ . Sh404sefHelperFiles::$stringDelimiter;
+ $line = Sh404sefHelperFiles::$stringDelimiter . $counter . $glue . $textRecord;
+ $data .= $line . "\n";
+ }
+ }
+
+ // prepare data for storage
+ if (!empty( $header)) {
+ // first record written to file, prepend header
+ $data = $header . $data;
+ }
+
+ // store in file
+ $status = Sh404sefHelperFiles::appendToFile( $this->_filename, $data);
+
+ // return any error
+ return $status;
+
+ }
+
+ protected function _getTerminateOptions() {
+
+ $options = '
';
+ $options .= '';
+ $options .= JText::_('COM_SH404SEF_PURGE_TEMP_FILES');
+ $options .= ' ';
+
+ return $options;
+ }
+
+}
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importaliases.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importaliases.php
new file mode 100644
index 00000000..9751865b
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importaliases.php
@@ -0,0 +1,96 @@
+_context = 'aliases';
+
+ // setup a few custom properties
+ $properties['_returnController'] = 'aliases';
+ $properties['_returnTask'] = '';
+ $properties['_returnView'] = 'aliases';
+ $properties['_returnLayout'] = 'default';
+
+ // and return the whole thing
+ return $properties;
+
+ }
+
+ /**
+ * Creates a record in the database, based
+ * on data read from import file
+ *
+ * @param array $header an array of fields, as built from the header line
+ * @param string $line raw record obtained from import file
+ */
+ protected function _createRecord( $header, $line) {
+
+ // extract the record
+ $line = $this->_lineToArray( $line);
+
+ // get table object to store record
+ $model = ShlMvcModel_Base::getInstance( 'aliases', 'Sh404sefModel');
+
+ // bind table to current record
+ $record = array();
+ $record['newurl'] = $line[3];
+ if ($record['newurl'] == '__ Homepage __') {
+ $record['newurl'] = sh404SEF_HOMEPAGE_CODE;
+ }
+ $record['alias'] = $line[1];
+ $record['type'] = $line[4];
+
+
+ // find if there is already same alias record for this non-sef url. If so
+ // we want the imported record to overwrite the existing one.
+ $existingRecords = $model->getByAttr( array( 'newurl' => $record['newurl'], 'alias' => $record['alias']));
+ if( !empty( $existingRecords)) {
+ $existingRecord = $existingRecords[0]; // getByAttr always returns an array
+
+ // use the existing id, this will be enought to override existing record when saving
+ $record['id'] = $existingRecord->id;
+
+ // ensure consistency : delete the remaining records, though there is no reason
+ // there can be more than one record with same alias AND same SEF
+ array_shift( $existingRecords);
+ if (!empty( $existingRecords)) {
+ ShlDbHelper::deleteIn( '#__sh404sef_aliases', 'id', $existingRecords, ShlDbHelper::INTEGER);
+ }
+ }
+
+ // save record : returns the record id, so failure is when 0 is returned
+ $saveId = $model->save( $record);
+ if(empty( $saveId)) {
+ // rethrow a more appropriate error message
+ throw new Sh404sefExceptionDefault( JText::sprintf( 'COM_SH404SEF_IMPORT_ERROR_INSERTING_INTO_DB', $line[0]));
+ }
+ }
+}
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importpageids.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importpageids.php
new file mode 100644
index 00000000..ab7893f8
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importpageids.php
@@ -0,0 +1,78 @@
+_context = 'pageids';
+
+ // setup a few custom properties
+ $properties['_returnController'] = 'pageids';
+ $properties['_returnTask'] = '';
+ $properties['_returnView'] = 'pageids';
+ $properties['_returnLayout'] = 'default';
+
+ // and return the whole thing
+ return $properties;
+
+ }
+
+ /**
+ * Creates a record in the database, based
+ * on data read from import file
+ *
+ * @param array $header an array of fields, as built from the header line
+ * @param string $line raw record obtained from import file
+ */
+ protected function _createRecord( $header, $line) {
+
+ // extract the record
+ $line = $this->_lineToArray( $line);
+
+ // get table object to store record
+ jimport( 'joomla.database.table');
+ $table = JTable::getInstance( 'pageids', 'Sh404sefTable');
+
+ // bind table to current record
+ $record = array();
+ $record['newurl'] = $line[3];
+ if ($record['newurl'] == '__ Homepage __') {
+ $record['newurl'] = sh404SEF_HOMEPAGE_CODE;
+ }
+ $record['pageid'] = $line[1];
+ $record['type'] = $line[4];
+
+ // save record
+ if (!$table->save( $record)) {
+ throw new Sh404sefExceptionDefault( JText::sprintf( 'COM_SH404SEF_IMPORT_ERROR_INSERTING_INTO_DB', $line[0]));
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importsh404sefmetas.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importsh404sefmetas.php
new file mode 100644
index 00000000..6e3000d5
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importsh404sefmetas.php
@@ -0,0 +1,123 @@
+_context = 'sh404sefmetas';
+
+ // setup a few custom properties
+ $properties['_returnController'] = 'urls';
+ $properties['_returnTask'] = '';
+ $properties['_returnView'] = 'urls';
+ $properties['_returnLayout'] = 'default';
+
+ // and return the whole thing
+ return $properties;
+
+ }
+
+ /**
+ * Creates a record in the database, based
+ * on data read from import file
+ *
+ * @param array $header an array of fields, as built from the header line
+ * @param string $line raw record obtained from import file
+ */
+ protected function _createRecord( $header, $line) {
+
+ // extract the record
+ $line = $this->_lineToArray( trim( $line));
+
+ // get table object to store record
+ $model = ShlMvcModel_Base::getInstance( 'metas', 'Sh404sefModel');
+
+ // bind table to current record
+ $record = array();
+ $record['newurl'] = $line[1];
+ $record['metatitle'] = $line[4];
+ $record['metadesc'] = $line[2];
+ $record['metakey'] = $line[3];
+ $record['metalang'] = $line[5];
+ $record['metarobots'] = $line[6];
+
+ // clean up records
+ foreach( $record as $key => $value) {
+ if ($value == ' ') {
+ $record[$key] = '';
+ }
+ }
+
+ // find if there is already an url record for this non-sef url. If so
+ // we want the imported record to overwrite the existing one.
+ // while makinf sure we're doing that with the main url, not one of the duplicates
+ $existingRecords = $model->getByAttr( array( 'newurl' => $record['newurl']));
+ if( !empty( $existingRecords)) {
+ $existingRecord = $existingRecords[0]; // getByAttr always returns an array
+
+ // use the existing id, this will be enought to override existing record when saving
+ $record['id'] = $existingRecord->id;
+
+ // ensure consistency : delete the remaining records, though there is no reason
+ // there can be more than one record with same SEF AND same SEF
+ array_shift( $existingRecords);
+ if (!empty( $existingRecords)) {
+ ShlDbHelper::deleteIn( '#__sh404sef_metas', 'id', $existingRecords, ShlDbHelper::INTEGER);
+
+ }
+ } else {
+ $record['id'] = 0;
+ }
+
+ // save record : returns the record id, so failure is when 0 is returned
+ $status = $model->save( $record);
+ if (!$status) {
+ // rethrow a more appropriate error message
+ throw new Sh404sefExceptionDefault( JText::sprintf( 'COM_SH404SEF_IMPORT_ERROR_INSERTING_INTO_DB', $line[0]));
+ }
+
+ }
+
+ /**
+ * Return html for any option that could
+ * be presented to the user on the last
+ * page of the wizard (like clean temp files)
+ * for instance. This will be displayed just after
+ * the mainText text, as prepared by the main
+ * part of this controller
+ */
+ protected function _getTerminateOptions() {
+
+ $options = JText::_( 'COM_SH404SEF_IMPORT_URLS_WARNING');
+
+ return $options;
+ }
+
+}
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importsh404sefurls.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importsh404sefurls.php
new file mode 100644
index 00000000..928557a4
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importsh404sefurls.php
@@ -0,0 +1,148 @@
+_context = 'sh404sefurls';
+
+ // setup a few custom properties
+ $properties['_returnController'] = 'urls';
+ $properties['_returnTask'] = '';
+ $properties['_returnView'] = 'urls';
+ $properties['_returnLayout'] = 'default';
+
+ // and return the whole thing
+ return $properties;
+
+ }
+
+ /**
+ * Creates a record in the database, based
+ * on data read from import file
+ *
+ * @param array $header an array of fields, as built from the header line
+ * @param string $line raw record obtained from import file
+ */
+ protected function _createRecord( $header, $line) {
+
+ // extract the record
+ $line = $this->_lineToArray( trim($line));
+
+ // get table object to store record
+ $model = ShlMvcModel_Base::getInstance( 'editurl', 'Sh404sefModel');
+
+ // bind table to current record
+ $record = array();
+ $record['oldurl'] = $line[3];
+ $record['newurl'] = $line[4];
+ if ($record['newurl'] == '__ Homepage __') {
+ $record['newurl'] = sh404SEF_HOMEPAGE_CODE;
+ }
+ $record['cpt'] = $line[1];
+ $record['rank'] = $line[2];
+ $record['dateadd'] = $line[5];
+ $record['metatitle'] = '';
+ $record['metadesc'] = '';
+ $record['metakey'] = '';
+ $record['metalang'] = '';
+ $record['metarobots'] = '';
+
+ // find if there is already an url record for this non-sef url. If so
+ // we want the imported record to overwrite the existing one.
+ // while makinf sure we're doing that with the main url, not one of the duplicates
+ $existingRecords = $model->getByAttr( array( 'newurl' => $record['newurl'], 'oldurl' => $record['oldurl']));
+ if( !empty( $existingRecords)) {
+ $existingRecord = $existingRecords[0]; // getByAttr always returns an array
+
+ // use the existing id, this will be enought to override existing record when saving
+ $record['id'] = $existingRecord->id;
+
+ // ensure consistency : delete the remaining records, though there is no reason
+ // there can be more than one record with same SEF AND same SEF
+ array_shift( $existingRecords);
+ if (!empty( $existingRecords)) {
+ ShlDbHelper::deleteIn( '#__sh404sef_urls', 'id', $existingRecords, ShlDbHelper::INTEGER);
+ }
+ } else {
+ $record['id'] = 0;
+ }
+
+ // find if we already have a meta data record for this non-sef url
+ // as we want to update it if so, instead of creating a new record
+ $metasModel = ShlMvcModel_Base::getInstance( 'metas', 'Sh404sefModel');
+ $existingMetas = $metasModel->getByAttr( array( 'newurl' => $record['newurl']));
+ if( !empty( $existingMetas)) {
+ $existingMeta = $existingMetas[0]; // getByAttr always returns an array
+
+ // use the existing id, this will be enought to override existing record when saving
+ $record['meta_id'] = $existingMeta->id;
+ } else {
+ $record['meta_id'] = 0;
+ }
+
+ // for aliases, we don't import them here, but we need to create a dummy
+ // record so as to preserve possible pre-existing aliases for the same non-sef url
+ $aliasesModel = ShlMvcModel_Base::getInstance( 'editalias', 'Sh404sefModel');
+ $existingAliases = $aliasesModel->getByAttr( array( 'newurl' => $record['newurl']));
+ $record['shAliasList'] = '';
+ if( !empty( $existingAliases)) {
+ foreach( $existingAliases as $existingAlias) {
+ // build up a text list, just as if we were to edit aliases
+ // as this is what the model expect to receive
+ $record['shAliasList'] .= $existingAlias->alias . "\n";
+ }
+ }
+
+ // save record : returns the record id, so failure is when 0 is returned
+ $savedId = $model->save( $record, sh404SEF_URLTYPE_AUTO);
+ if (empty( $savedId)) {
+ // rethrow a more appropriate error message
+ throw new Sh404sefExceptionDefault( JText::sprintf( 'COM_SH404SEF_IMPORT_ERROR_INSERTING_INTO_DB', $line[0]));
+ }
+
+ }
+
+ /**
+ * Return html for any option that could
+ * be presented to the user on the last
+ * page of the wizard (like clean temp files)
+ * for instance. This will be displayed just after
+ * the mainText text, as prepared by the main
+ * part of this controller
+ */
+ protected function _getTerminateOptions() {
+
+ $options = JText::_( 'COM_SH404SEF_IMPORT_URLS_WARNING');
+
+ return $options;
+ }
+
+}
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importurls.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importurls.php
new file mode 100644
index 00000000..8514858f
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/importurls.php
@@ -0,0 +1,149 @@
+_context = 'urls';
+
+ // setup a few custom properties
+ $properties['_returnController'] = 'urls';
+ $properties['_returnTask'] = '';
+ $properties['_returnView'] = 'urls';
+ $properties['_returnLayout'] = 'default';
+
+ // and return the whole thing
+ return $properties;
+
+ }
+
+ /**
+ * Creates a record in the database, based
+ * on data read from import file
+ *
+ * @param array $header an array of fields, as built from the header line
+ * @param string $line raw record obtained from import file
+ */
+ protected function _createRecord( $header, $line) {
+
+ // extract the record
+ $line = $this->_lineToArray( $line);
+
+ // get table object to store record
+ $model = ShlMvcModel_Base::getInstance( 'editurl', 'Sh404sefModel');
+
+ // bind table to current record
+ $record = array();
+ $record['oldurl'] = $line[1];
+ $record['newurl'] = $line[2];
+ if ($record['newurl'] == '__ Homepage __') {
+ $record['newurl'] = sh404SEF_HOMEPAGE_CODE;
+ }
+ $record['cpt'] = $line[3];
+ $record['rank'] = $line[4];
+ $record['dateadd'] = $line[5];
+ $record['metatitle'] = $line[6];
+ $record['metadesc'] = $line[7];
+ $record['metakey'] = $line[8];
+ $record['metalang'] = $line[9];
+ $record['metarobots'] = $line[10];
+ $record['canonical'] = $line[11];
+
+ // find if there is already an url record for this non-sef url. If so
+ // we want the imported record to overwrite the existing one.
+ // while makinf sure we're doing that with the main url, not one of the duplicates
+ $existingRecords = $model->getByAttr( array( 'newurl' => $record['newurl'], 'oldurl' => $record['oldurl']));
+ if( !empty( $existingRecords)) {
+ $existingRecord = $existingRecords[0]; // getByAttr always returns an array
+
+ // use the existing id, this will be enought to override existing record when saving
+ $record['id'] = $existingRecord->id;
+
+ // ensure consistency : delete the remaining records, though there is no reason
+ // there can be more than one record with same SEF AND same SEF
+ array_shift( $existingRecords);
+ if (!empty( $existingRecords)) {
+ ShlDbHelper::deleteIn( '#__sh404sef_urls', 'id', $existingRecords, ShlDbHelper::INTEGER);
+ }
+ } else {
+ $record['id'] = 0;
+ }
+
+ // find if we already have a meta data record for this non-sef url
+ // as we want to update it if so, instead of creating a new record
+ $metasModel = ShlMvcModel_Base::getInstance( 'metas', 'Sh404sefModel');
+ $existingMetas = $metasModel->getByAttr( array( 'newurl' => $record['newurl']));
+ if( !empty( $existingMetas)) {
+ $existingMeta = $existingMetas[0]; // getByAttr always returns an array
+
+ // use the existing id, this will be enought to override existing record when saving
+ $record['meta_id'] = $existingMeta->id;
+ } else {
+ $record['meta_id'] = 0;
+ }
+
+ // for aliases, we don't import them here, but we need to create a dummy
+ // record so as to preserve possible pre-existing aliases for the same non-sef url
+ $aliasesModel = ShlMvcModel_Base::getInstance( 'editalias', 'Sh404sefModel');
+ $existingAliases = $aliasesModel->getByAttr( array( 'newurl' => $record['newurl']));
+ $record['shAliasList'] = '';
+ if( !empty( $existingAliases)) {
+ foreach( $existingAliases as $existingAlias) {
+ // build up a text list, just as if we were to edit aliases
+ // as this is what the model expect to receive
+ $record['shAliasList'] .= $existingAlias->alias . "\n";
+ }
+ }
+
+ // save record : returns the record id, so failure is when 0 is returned
+ $savedId = $model->save( $record, sh404SEF_URLTYPE_AUTO);
+ if (empty( $savedId)) {
+ // rethrow a more appropriate error message
+ throw new Sh404sefExceptionDefault( JText::sprintf( 'COM_SH404SEF_IMPORT_ERROR_INSERTING_INTO_DB', $line[0]));
+ }
+
+ }
+
+ /**
+ * Return html for any option that could
+ * be presented to the user on the last
+ * page of the wizard (like clean temp files)
+ * for instance. This will be displayed just after
+ * the mainText text, as prepared by the main
+ * part of this controller
+ */
+ protected function _getTerminateOptions() {
+
+ $options = JText::_( 'COM_SH404SEF_IMPORT_URLS_WARNING');
+
+ return $options;
+ }
+
+}
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/index.html b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/index.html
new file mode 100644
index 00000000..fa6d84e8
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/index.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/adapters/joomsefinstaller.php b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/joomsefinstaller.php
new file mode 100644
index 00000000..075e072b
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/adapters/joomsefinstaller.php
@@ -0,0 +1,80 @@
+parent->getPath( 'source');
+ $path = $source . '/' . $this->_getElement() . '.xml';
+ $fileContent = JFile::read( $path);
+ if(!empty( $fileContent)) {
+ $fileContent = str_replace( 'type="sef_ext"', 'type="sef_ext" method="upgrade"', $fileContent);
+
+ $defaults = array();
+ $remoteConfig = Sh404sefHelperUpdates::getRemoteConfig( $forced = false);
+ $remotes = empty($remoteConfig->config['joomsef_prefixes']) ? array() : $remoteConfig->config['joomsef_prefixes'];
+ $prefixes = array_unique( array_merge( $defaults, $remotes));
+ foreach( $prefixes as $prefix) {
+ $fileContent = preg_replace( '/function\s*' . preg_quote( $prefix) . '\s*\(\s*\)\s*\{/isU', 'function ' . $prefix . '() { return;', $fileContent);
+ }
+ // generic replace
+ $defaultReplaces = array();
+ $remoteReplaces = empty($remoteConfig->config['joomsef_prefixes']) ? array() : $remoteConfig->config['joomsef_prefixes'];
+ $replaces = array_unique( array_merge( $defaultReplaces, $remoteReplaces));
+ foreach( $replaces as $replace) {
+ $fileContent = preg_replace( '/' . $replace['source'] . '/sU', $replace['target'], $fileContent);
+ }
+
+ // group="seo" is of no use for us, so leave it behind
+ $written = JFile::write( $path, $fileContent);
+ }
+
+ // fix in memory object, by killing it, thus prompting recreation
+ $manifest = $this->parent->getManifest();
+ $manifest = null;
+ $manifest = $this->parent->getManifest();
+ $this->manifest = & $manifest->document;
+
+ }
+
+ /**
+ * Get unique element id for the plugin
+ *
+ */
+ protected function _getElement($xml) {
+
+ $element = parent::_getElement( $xml);
+
+ return 'com_' . $element;
+
+ }
+}
\ No newline at end of file
diff --git a/deployed/sh404sef/administrator/components/com_sh404sef/admin.sh404sef.php b/deployed/sh404sef/administrator/components/com_sh404sef/admin.sh404sef.php
new file mode 100644
index 00000000..f73ff3de
--- /dev/null
+++ b/deployed/sh404sef/administrator/components/com_sh404sef/admin.sh404sef.php
@@ -0,0 +1,23 @@
+