Advertisement
Guest User

Piwik fast // archive

a guest
Nov 27th, 2012
403
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 39.91 KB | None | 0 0
  1. <?php
  2. /*
  3. * @SEE : http://forum.piwik.org/read.php?3,97712
  4. */
  5. $USAGE = "
  6. Usage:
  7.     /path/to/cli/php \"".@$_SERVER['argv'][0]."\" --url=http://your-website.org/path/to/piwik/ [arguments]
  8.  
  9. Arguments:
  10.     --url=[piwik-server-url]
  11.             Mandatory argument. Must be set to the Piwik base URL.
  12.             For example: --url=http://analytics.example.org/ or --url=https://example.org/piwik/
  13.     --force-all-websites
  14.             If specified, the script will trigger archiving on all websites.
  15.             This can be used along with --force-all-periods to trigger archiving on all websites and all periods.
  16.     --force-all-periods[=seconds]
  17.             Triggers archiving on websites with some traffic in the last [seconds] seconds.
  18.             [seconds] defaults to 604800 which is 7 days.
  19.             For example: --force-all-periods=86400 will archive websites that had visits in the last 24 hours.
  20.     --force-timeout-for-periods=[seconds]
  21.             The current week/ current month/ current year will be processed at most every [seconds].
  22.             If not specified, defaults to 3600.
  23.     --accept-invalid-ssl-certificate
  24.             It is _NOT_ recommended to use this argument. Instead, you should use a valid SSL certificate!
  25.             It can be useful if you specified --url=https://... or if you are using Piwik with force_ssl=1
  26.     --help
  27.             Displays usage
  28.  
  29. Notes:
  30.     * It is recommended to run the script with the argument --url=[piwik-server-url] only. Other arguments are not required.
  31.     * This script should be executed every hour via crontab, or as a deamon.
  32.     * You can also run it via http:// by specifying the Super User &token_auth=XYZ as a parameter ('Web Cron'),
  33.       but it is recommended to run it via command line/CLI instead.
  34.     * If you use Piwik to track dozens/hundreds of websites, please let the team know at hello@piwik.org
  35.       it makes us happy to learn successful user stories :)
  36.     * Enjoy!
  37. ";
  38. /*
  39. Ideas for improvements:
  40.     - Feature request: Add option to log completion even with errors: archive_script_ignore_errors = 0
  41.     - Known bug: when adding new segments to preprocess, script will assume that data was processed for this segment in the past
  42.     - Document: how to run the script as a daemon for near real time / constant processing
  43.     - The script can be executed multiple times in parrallel but with known issues:
  44.       - scheduled task could send multiple reports
  45.       - there is no documentation
  46.       - it does not work well with --force-all-periods etc.
  47.     - Possible performance improvement: Run first websites which are faster to process (weighted by visits and/or time to generate the last daily report)
  48.       This would make sure that huge websites do not 'block' processing of smaller websites' reports.
  49.     - Core: check that on first day of month, if request last month from UI,
  50.       it returns last temporary monthly report generated, if the last month haven't yet been processed / finalized
  51.  */
  52. define('PIWIK_INCLUDE_PATH', realpath( dirname(__FILE__)."/../.." ));
  53. define('PIWIK_USER_PATH', PIWIK_INCLUDE_PATH);
  54. define('PIWIK_ENABLE_DISPATCH', false);
  55. define('PIWIK_ENABLE_ERROR_HANDLER', false);
  56. define('PIWIK_ENABLE_SESSION_START', false);
  57. define('PIWIK_MODE_ARCHIVE', true);
  58. require_once PIWIK_INCLUDE_PATH . "/index.php";
  59. require_once PIWIK_INCLUDE_PATH . "/core/API/Request.php";
  60.  
  61. try {
  62.     $archiving = new Archiving;
  63.     $archiving->init();
  64.     $archiving->run();
  65.     $archiving->end();
  66. } catch(Exception $e) {
  67.     $archiving->logFatalError($e->getMessage());
  68. }
  69.  
  70. class Archiving
  71. {
  72.     const OPTION_ARCHIVING_FINISHED_TS = "LastCompletedFullArchiving";
  73.     const TRUNCATE_ERROR_MESSAGE_SUMMARY = 400;
  74.  
  75.     // Seconds window to look back to define "active websites" to archive on the first archive.php script execution
  76.     protected $firstRunActiveWebsitesWithTraffic = 604800; // 7 days
  77.  
  78.     // By default, we only process the current week/month/year at most once an hour
  79.     protected $processPeriodsMaximumEverySeconds = 3600;
  80.  
  81.     protected $websiteDayHasFinishedSinceLastRun = array();
  82.     protected $idSitesInvalidatedOldReports = array();
  83.     protected $piwikUrl = false;
  84.     protected $token_auth = false;
  85.     protected $visits = 0;
  86.     protected $requests = 0;
  87.     protected $output = '';
  88.     protected $shouldResetState = false;
  89.     protected $shouldArchiveAllWebsites = false;
  90.     protected $acceptInvalidSSLCertificate = false;
  91.     // basic setup
  92.     protected $is_win = 1;
  93.     protected $archive_cli = true;
  94.     protected $phpbin = '/usr/bin/php';
  95.     // time out in sec
  96.     protected $proc_time_out = 600;
  97.     /**
  98.      * By default, will process last 52 days/weeks/months/year.
  99.      * It will be overwritten by the number of days since last archiving ran until completion.
  100.      */
  101.     const DEFAULT_DATE_LAST = 52;
  102.  
  103.     protected $timeLastCompleted = false;
  104.     protected $requestPrepend = '&trigger=archivephp';
  105.     protected $errors = array();
  106.  
  107.     public function init()
  108.     {
  109.         $this->initCore();
  110.         $this->initTokenAuth();
  111.         $this->initCheckCli();
  112.         $this->initLog();
  113.         $this->displayHelp();
  114.         $this->initPiwikHost();
  115.         $this->initStateFromParameters();
  116.         Piwik::setUserIsSuperUser(true);
  117.  
  118.         $this->logSection("INIT");
  119.         $this->log("Querying Piwik API at: {$this->piwikUrl}");
  120.         $this->log("Running as Super User: " . $this->login);
  121.  
  122.         $this->acceptInvalidSSLCertificate = $this->isParameterSet("accept-invalid-ssl-certificate");
  123.  
  124.         // Test the specified piwik URL is valid
  125.         $response = $this->request("?module=API&method=API.getDefaultMetricTranslations&format=php");
  126.         $responseUnserialized = @unserialize($response);
  127.         if($response === false
  128.             || !is_array($responseUnserialized))
  129.         {
  130.             $this->logFatalError("The Piwik URL {$this->piwikUrl} does not seem to be pointing to a Piwik server. Response was '$response'.");
  131.         }
  132.  
  133.         $this->log("Notes");
  134.         // Information about timeout
  135.         $this->todayArchiveTimeToLive = Piwik_ArchiveProcessing::getTodayArchiveTimeToLive();
  136.         $this->log("- Reports for today will be processed at most every ".Piwik_ArchiveProcessing::getTodayArchiveTimeToLive()
  137.                     ." seconds. You can change this value in Piwik UI > Settings > General Settings.");
  138.         $this->log("- Reports for the current week/month/year will be refreshed at most every "
  139.                     .$this->processPeriodsMaximumEverySeconds." seconds.");
  140.         // Fetching segments to process
  141.         $this->segments = Piwik_CoreAdminHome_API::getInstance()->getKnownSegmentsToArchive();
  142.         if(empty($this->segments)) $this->segments = array();
  143.         if(!empty($this->segments))
  144.         {
  145.             $this->log("- Segments to pre-process for each website and each period: ". implode(", ", $this->segments));
  146.         }
  147.  
  148.         // Try and not request older data we know is already archived
  149.         if($this->timeLastCompleted !== false)
  150.         {
  151.             $dateLast = time() - $this->timeLastCompleted;
  152.             $this->log("- Archiving was last executed without error ".Piwik::getPrettyTimeFromSeconds($dateLast, true, $isHtml = false)." ago");
  153.         }
  154.  
  155.         $this->initWebsitesToProcess();
  156.         flush();
  157.     }
  158.  
  159.     /**
  160.      * Returns URL to process reports for the $idsite on a given period with no segment
  161.      */
  162.     protected function getVisitsRequestUrl($idsite, $period, $lastTimestampWebsiteProcessed = false)
  163.     {
  164.         if(empty($lastTimestampWebsiteProcessed))
  165.         {
  166.             $dateLast = self::DEFAULT_DATE_LAST;
  167.         }
  168.         else
  169.         {
  170.             // Enforcing last2 at minimum to work around timing issues and ensure we make most archives available
  171.             $dateLast = floor( (time() - $lastTimestampWebsiteProcessed) / 86400) + 2;
  172.             if($dateLast > self::DEFAULT_DATE_LAST)
  173.             {
  174.                 $dateLast = self::DEFAULT_DATE_LAST;
  175.             }
  176.         }
  177.         return "?module=API&method=VisitsSummary.getVisits&idSite=$idsite&period=$period&date=last".$dateLast."&format=php&token_auth=".$this->token_auth;
  178.     }
  179.  
  180.     protected function lastRunKey($idsite, $period)
  181.     {
  182.         return "lastRunArchive". $period ."_". $idsite;
  183.     }
  184.  
  185.     /**
  186.      * Main function, runs archiving on all websites with new activity
  187.      */
  188.     public function run()
  189.     {
  190.         $websitesWithVisitsSinceLastRun =
  191.             $skippedPeriodsArchivesWebsite =
  192.             $skippedDayArchivesWebsites =
  193.             $skipped =
  194.             $processed =
  195.             $archivedPeriodsArchivesWebsite = 0;
  196.         $timer = new Piwik_Timer;
  197.  
  198.         $this->logSection("START");
  199.         $this->log("Starting Piwik reports archiving...");
  200.         // start with trashing old logs if any
  201.         $archives_log = $this->list_dir(PW_ABS_PATH . 'tmp', '`^archive_[a-z0-9]+\.(log|lock)$`i');
  202.         foreach($archives_log as $archives_log_file) {
  203.             @unlink(PW_ABS_PATH . 'tmp/' . $archives_log_file);
  204.         }
  205.  
  206.         $this->websites = array_unique($this->websites);
  207.         foreach ($this->websites as $idsite)
  208.         {
  209.             flush();
  210.             $requestsBefore = $this->requests;
  211.             if ($idsite <= 0)
  212.             {
  213.                 continue;
  214.             }
  215.  
  216.             $timerWebsite = new Piwik_Timer;
  217.  
  218.             $lastTimestampWebsiteProcessedPeriods = $lastTimestampWebsiteProcessedDay = false;
  219.             if(!$this->shouldResetState)
  220.             {
  221.                 $lastTimestampWebsiteProcessedPeriods = Piwik_GetOption( $this->lastRunKey($idsite, "periods") );
  222.                 $lastTimestampWebsiteProcessedDay = Piwik_GetOption( $this->lastRunKey($idsite, "day") );
  223.             }
  224.  
  225.             // For period other than days, we only re-process the reports at most
  226.             // 1) every $processPeriodsMaximumEverySeconds
  227.             $secondsSinceLastExecution = time() - $lastTimestampWebsiteProcessedPeriods;
  228.  
  229.             // if timeout is more than 10 min, we account for a 5 min processing time, and allow trigger 1 min earlier
  230.             if($this->processPeriodsMaximumEverySeconds > 10 * 60)
  231.             {
  232.                 $secondsSinceLastExecution += 5 * 60;
  233.             }
  234.             $shouldArchivePeriods = $secondsSinceLastExecution > $this->processPeriodsMaximumEverySeconds;
  235.             if(empty($lastTimestampWebsiteProcessedPeriods))
  236.             {
  237.                 // 2) OR always if script never executed for this website before
  238.                 $shouldArchivePeriods = true;
  239.             }
  240.  
  241.  
  242.             // (*) If the website is archived because it is a new day in its timezone
  243.             // We make sure all periods are archived, even if there is 0 visit today
  244.             if(in_array($idsite, $this->websiteDayHasFinishedSinceLastRun))
  245.             {
  246.                 $shouldArchivePeriods = true;
  247.             }
  248.  
  249.             // (*) If there was some old reports invalidated for this website
  250.             // we make sure all these old reports are triggered at least once
  251.             $websiteIsOldDataInvalidate = in_array($idsite, $this->idSitesInvalidatedOldReports);
  252.             if($websiteIsOldDataInvalidate)
  253.             {
  254.                 $shouldArchivePeriods = true;
  255.             }
  256.  
  257.             // Test if we should process this website at all
  258.             $elapsedSinceLastArchiving = time() - $lastTimestampWebsiteProcessedDay;
  259.             if( $elapsedSinceLastArchiving < $this->todayArchiveTimeToLive)
  260.             {
  261.                 $this->log("Skipped website id $idsite, already processed today's report in recent run, "
  262.                     .Piwik::getPrettyTimeFromSeconds($elapsedSinceLastArchiving, true, $isHtml = false)
  263.                     ." ago, ".$timerWebsite->__toString());
  264.                 $skippedDayArchivesWebsites++;
  265.                 $skipped++;
  266.                 continue;
  267.             }
  268.  
  269.             // Fake that the request is already done, so that other archive.php
  270.             // running do not grab the same website from the queue
  271.             Piwik_SetOption( $this->lastRunKey($idsite, "day"), time() );
  272.  
  273.             $url = $this->getVisitsRequestUrl($idsite, "day",
  274.                                 // when some data was purged from this website
  275.                                 // we make sure we query all previous days/weeks/months
  276.                             $websiteIsOldDataInvalidate
  277.                                 // when --force-all-websites option,
  278.                                 // also forces to archive last52 days to be safe
  279.                             || $this->shouldArchiveAllWebsites
  280.                                 ? false
  281.                                 : $lastTimestampWebsiteProcessedDay
  282.             );
  283.             $content = $this->request($url);
  284.             $response = @unserialize($content);
  285.  
  286.             if(empty($content)
  287.                 || !is_array($response)
  288.                 || count($response) == 0)
  289.             {
  290.                 // cancel the succesful run flag
  291.                 Piwik_SetOption( $this->lastRunKey($idsite, "day"), 0 );
  292.  
  293.                 $this->log("WARNING: Empty or invalid response '$content' for website id $idsite, ".$timerWebsite->__toString().", skipping");
  294.                 $skipped++;
  295.                 continue;
  296.             }
  297.             $visitsToday = end($response);
  298.             $this->requests++;
  299.             $processed++;
  300.  
  301.             // If there is no visit today and we don't need to process this website, we can skip remaining archives
  302.             if($visitsToday <= 0
  303.                 && !$shouldArchivePeriods)
  304.             {
  305.                 $this->log("Skipped website id $idsite, no visit today, ".$timerWebsite->__toString());
  306.                 $skipped++;
  307.                 continue;
  308.             }
  309.  
  310.             $visitsAllDays = array_sum($response);
  311.             if($visitsAllDays == 0
  312.                 && !$shouldArchivePeriods
  313.                 && $this->shouldArchiveAllWebsites
  314.             )
  315.             {
  316.                 $this->log("Skipped website id $idsite, no visits in the last ".count($response)." days, ".$timerWebsite->__toString());
  317.                 $skipped++;
  318.                 continue;
  319.             }
  320.             $this->visits += $visitsToday;
  321.             $websitesWithVisitsSinceLastRun++;
  322.             // This part is to get the relevant country list and only applies
  323.             // to the country segmentation case
  324.             // get relevant country list to include in segments with 2 day security
  325.             $result = Piwik_FetchAll(
  326.                 "SELECT DISTINCT(location_country)
  327.                     FROM ".Piwik_Common::prefixTable('log_visit') . "
  328.                     WHERE idsite = $idsite
  329.                     AND visit_last_action_time > ?", array($lastTimestampWebsiteProcessedDay - 172800));
  330.             $site_country_segments = array();
  331.             foreach ($result as $row) {
  332.                 $site_country_segments[] = 'country==' . $row['location_country'];
  333.             }
  334.             // $site_country_segments is passed to archiveVisitsAndSegments to reuse
  335.             // it for each periods
  336.             $this->archiveVisitsAndSegments($idsite, "day", $lastTimestampWebsiteProcessedDay, $timerWebsite, $site_country_segments);
  337.  
  338.             if($shouldArchivePeriods)
  339.             {
  340.                 $success = true;
  341.                 foreach (array('week', 'month', 'year') as $period)
  342.                 {
  343.                     // Again, $site_country_segments is passed to archiveVisitsAndSegments to reuse
  344.                     // it for each periods
  345.                     $success = $this->archiveVisitsAndSegments($idsite, $period, $lastTimestampWebsiteProcessedPeriods, null, $site_country_segments) && $success;
  346.                 }
  347.                 // Record succesful run of this website's periods archiving
  348.                 if($success)
  349.                 {
  350.                     Piwik_SetOption( $this->lastRunKey($idsite, "periods"), time() );
  351.  
  352.                     // Remove this website from the list of websites to be invalidated
  353.                     // since it's now just been re-processing the reports, job is done!
  354.                     if( in_array($idsite, $this->idSitesInvalidatedOldReports ) )
  355.                     {
  356.                         $websiteIdsInvalidated = Piwik_CoreAdminHome_API::getWebsiteIdsToInvalidate();
  357.  
  358.                         if(count($websiteIdsInvalidated))
  359.                         {
  360.                             $found = array_search($idsite, $websiteIdsInvalidated);
  361.                             if($found!==false)
  362.                             {
  363.                                 unset($websiteIdsInvalidated[$found]);
  364. //                              $this->log("Websites left to invalidate: " . implode(", ", $websiteIdsInvalidated));
  365.                                 Piwik_SetOption(Piwik_CoreAdminHome_API::OPTION_INVALIDATED_IDSITES, serialize($websiteIdsInvalidated));
  366.                             }
  367.                         }
  368.                     }
  369.                 }
  370.                 $archivedPeriodsArchivesWebsite++;
  371.             }
  372.             else
  373.             {
  374.                 $skippedPeriodsArchivesWebsite++;
  375.             }
  376.             $requestsWebsite = $this->requests - $requestsBefore;
  377.  
  378.             $debug = $this->shouldArchiveAllWebsites ? ", last days = $visitsAllDays visits" : "";
  379.             Piwik::log("Archived website id = $idsite, today = $visitsToday visits"
  380.                             .$debug.", $requestsWebsite API requests, "
  381.                             // add segment count
  382.                             . count($site_country_segments) . " country segments, "
  383.                             . $timerWebsite->__toString()
  384.                             ." [" . ($websitesWithVisitsSinceLastRun+$skipped) . "/"
  385.                             . count($this->websites)
  386.                             . " done]" );
  387.         }
  388.  
  389.         $this->log("Done archiving!");
  390.  
  391.         $this->logSection("SUMMARY");
  392.         $this->log("Total daily visits archived: ". $this->visits);
  393.  
  394.         $totalWebsites = count($this->allWebsites);
  395.         $skipped = $totalWebsites - $websitesWithVisitsSinceLastRun;
  396.         $this->log("Archived today's reports for $websitesWithVisitsSinceLastRun websites");
  397.         $this->log("Archived week/month/year for $archivedPeriodsArchivesWebsite websites. ");
  398.         $this->log("Skipped $skipped websites: no new visit since the last script execution");
  399.         $this->log("Skipped $skippedDayArchivesWebsites websites day archiving: existing daily reports are less than {$this->todayArchiveTimeToLive} seconds old");
  400.         $this->log("Skipped $skippedPeriodsArchivesWebsite websites week/month/year archiving: existing periods reports are less than {$this->processPeriodsMaximumEverySeconds} seconds old");
  401.         $this->log("Total API requests: $this->requests");
  402.  
  403.         //DONE: done/total, visits, wtoday, wperiods, reqs, time, errors[count]: first eg.
  404.         $percent = count($this->websites) == 0
  405.                         ? ""
  406.                         : " ".round($processed * 100 / count($this->websites),0) ."%";
  407.         $otherInParallel = $skippedDayArchivesWebsites;
  408.         $this->log("done: ".
  409.                     $processed ."/". count($this->websites) . "" . $percent. ", ".
  410.                     $this->visits." v, $websitesWithVisitsSinceLastRun wtoday, $archivedPeriodsArchivesWebsite wperiods, ".
  411.                     $this->requests." req, ".round($timer->getTimeMs())." ms, ".
  412.                     (empty($this->errors)
  413.                         ? "no error"
  414.                         : (count($this->errors) . " errors. eg. '". reset($this->errors)."'" ))
  415.                     );
  416.         $this->log($timer->__toString());
  417.         $this->logSection("SCHEDULED TASKS");
  418.         $this->log("Starting Scheduled tasks... ");
  419.  
  420.         $tasksOutput = $this->request("?module=API&method=CoreAdminHome.runScheduledTasks&format=csv&convertToUnicode=0&token_auth=".$this->token_auth);
  421.         if($tasksOutput == "No data available")
  422.         {
  423.             $tasksOutput = " No task to run";
  424.         }
  425.         $this->log($tasksOutput);
  426.         $this->log("done");
  427.     }
  428.  
  429.     /**
  430.      * @return bool True on success, false if some request failed
  431.      * Main change in the logic is to first build the whole url list to process
  432.      * and then process them
  433.      */
  434.     private function archiveVisitsAndSegments($idsite, $period, $lastTimestampWebsiteProcessed, Piwik_Timer $timerWebsite = null, $site_segments = array())
  435.     {
  436.         $timer = new Piwik_Timer;
  437.         $url = $this->getVisitsRequestUrl($idsite, $period, $lastTimestampWebsiteProcessed) . $this->requestPrepend;
  438.         $urlNoSegment = $url;
  439.         $urls = array();
  440.         // already processed above for "day"
  441.         if ($period != "day") {
  442.             $urls[] = $url;
  443.         }
  444.  
  445.         foreach ($this->segments as $segment) {
  446.             $urls[] = $url.'&segment='.urlencode($segment);
  447.         }
  448.  
  449.         foreach ($site_segments as $segment) {
  450.             $urls[] = $url.'&segment='.urlencode($segment);
  451.         }
  452.         if (empty($urls)) {
  453.             return true;
  454.         }
  455.         $visitsAllDaysInPeriod = false;
  456.         if ($this->archive_cli) {
  457.             // multi cli process
  458.             $success = $this->CLI_archiveVisitsAndSegments($urls, $urlNoSegment, $visitsAllDaysInPeriod);
  459.         } else {
  460.             // multi HTTP requests
  461.             $success = $this->HTTP_archiveVisitsAndSegments($urls, $urlNoSegment, $visitsAllDaysInPeriod);
  462.         }
  463.         $this->log("Archived website id = $idsite, period = $period, "
  464.                     . ($period != "day" ? (int)$visitsAllDaysInPeriod. " visits, " : "" )
  465.                     . (!empty($timerWebsite) ? $timerWebsite->__toString() : $timer->__toString()));
  466.         return $success;
  467.     }
  468.     /**
  469.     * @return bool True on success, false if some request failed
  470.     */
  471.     private function CLI_archiveVisitsAndSegments($qs_batch, $urlNoSegment, & $visitsAllDaysInPeriod) {
  472.         if (empty($qs_batch) || !is_array($qs_batch)) {
  473.             return false;
  474.         }
  475.         // the maximum number of simultaneous processes
  476.         $max_process = 5;
  477.         $num_run_process = 0;
  478.         $success = true;
  479.         $visitsAllDaysInPeriod = false;
  480.         $running = null;
  481.         $processes = array();
  482.         $num_process = 0;
  483.         $num_echo = 0;
  484.         do {
  485.  
  486.             // if there are still processes in the queue
  487.             foreach ($qs_batch as $k => $qs) {
  488.                 // if we do not have too many workers already
  489.                 if ($num_process >= $max_process) {
  490.                     break;
  491.                 }
  492.                 // let's add a new process in background
  493.                 $_cmd = str_replace('\\', '/', $this->phpbin . ' ' . PW_ABS_PATH . 'index.php -- ' . trim($qs, '?& ') . $this->requestPrepend);
  494.                 $process_id = md5($_cmd);
  495.                 $process_file = str_replace('\\', '/', PW_ABS_PATH . 'tmp/archive_' . $process_id);
  496.                 $processes[$process_id] = array('qs_key' => $k, 'cmd' => $_cmd, 'started' => time());
  497.                 // Here we implement a very basic lock mechanism for // processes.
  498.                 // each process starts with creating a lock file,
  499.                 // then do the job, write the result in a log file, and then delete the lock file.
  500.                 // It's ok for our purpose since we only want to be sure that the process
  501.                 // ended before we read the result in the log file and this
  502.                 // guarantees it to us as long as we do not //ise the parent process itself
  503.                 if ($this->is_win) {
  504.                     // this is a mixture inspired from http://www.php.net/manual/fr/function.exec.php#110131
  505.                     // and http://www.php.net/manual/fr/function.popen.php#97431
  506.                     // generate batch file to be able to set up a very simple yet powerfull enough
  507.                     // lock mechanism
  508.                 /*  $bat_filename = "$process_file.bat";
  509.                     $handle = fopen($bat_filename, "w+");
  510.                     if ($handle && @flock($handle, LOCK_EX)) {
  511.                         fwrite($handle, "@echo off\n");
  512.                         // our lock file we can later test
  513.                         fwrite($handle, "echo. 2> \"$process_file.lock\"\n");
  514.                         fwrite($handle, escapeshellcmd($_cmd) . ' > "' . $process_file . '.log" 2>&1 ' . "\n");
  515.                         // task finished, delet lock file
  516.                         // for some *unknown* reasons slashes have to be inverted here
  517.                         fwrite($handle, "DEL " . str_replace('/', '\\', $process_file) . ".lock\n");
  518.                         fwrite($handle, "EXIT\n");
  519.                         @flock($handle, LOCK_UN);
  520.                         fclose($handle);
  521.                     }
  522.                     pclose(popen('start /B cmd /S /C "' . $bat_filename . '"' , 'r'));*/
  523.                     // same thing in one line to save IO
  524.                     pclose(popen('start /B cmd /S /C "echo. 2> "' . $process_file . '.lock" & ' . escapeshellcmd($_cmd) . ' > "' . $process_file . '.log" 2>&1 & DEL "' . str_replace('/', '\\', $process_file) . '.lock" & EXIT"' , 'r'));
  525.                 } else {
  526.                     exec(
  527.                         // create lock file
  528.                         '(touch ' . $process_file . '.lock && ' .
  529.                         // do the job, log both resutl and error in the same log file
  530.                         // a simple way to destroy the result if there are errors (even unrelated => safest)
  531.                         escapeshellcmd($_cmd) . ' > ' . $process_file . '.log 2>&1 && ' .
  532.                         // remove our lock once the job is actually done
  533.                         'rm ' . $process_file . '.lock)' .
  534.                         // sinced we wrapped all these commands in one line,
  535.                         // trash the whole output (that is none if everything works as expected)
  536.                         // since we need to hande it to create //isme
  537.                         ' > /dev/null & exit'
  538.                     );
  539.                 }
  540.                 unset($qs_batch[$k]);
  541.                 $this->requests++;
  542.                 $num_process++;
  543.                 $num_run_process++;
  544.                 usleep(10000);
  545.             }
  546.             usleep(10000);
  547.             clearstatcache(true);
  548.             foreach ($processes as $process_id => $process) {
  549.                 $qs = $processes[$process_id]['qs_key'];
  550.                 $process_file = PW_ABS_PATH . 'tmp/archive_' . $process_id;
  551.                 $log_file = $process_file . '.log';
  552.                 if ($num_echo > 50) {
  553.                     echo "\n";
  554.                     $num_echo = 0;
  555.                 }
  556.                 if (file_exists("$process_file.lock")) {
  557.                     if ($processes[$process_id]['started'] + $this->proc_time_out < time()) {
  558.                         $success = false;
  559.                         echo 'T';
  560.                         $num_echo++;
  561.                         unset($processes[$process_id]);
  562.                         $this->logError("Archive site process time outed: " . $process_id);
  563.                     }
  564.                     usleep(50000);
  565.                     continue;
  566.                 }
  567.                 usleep(50000);
  568.                 if (($log_file_size = @filesize($log_file)) > 8) {
  569.                     $handle = @fopen($log_file, 'r+b');
  570.                     if ($handle) {
  571.                         if (@flock($handle, LOCK_EX)) {
  572.                             // ok this task is finished
  573.                             $content = fread($handle, $log_file_size);
  574.                             $num_process--;
  575.                             unset($processes[$process_id]);
  576.                             flock($handle, LOCK_UN);
  577.                             $successResponse = $this->checkResponse($content, $qs);
  578.                             $success = $successResponse && $success;
  579.                             echo $successResponse ? '.' : '-';
  580.                             $num_echo++;
  581.                             if ($qs == $urlNoSegment && $successResponse) {
  582.                                 $stats = unserialize($content);
  583.                                 if (!is_array($stats)) {
  584.                                     $this->logError("Error unserializing the following response: " . $content);
  585.                                 }
  586.                                 $visitsAllDaysInPeriod = @array_sum($stats);
  587.                             }
  588.                             fclose($handle);
  589.                             unlink($log_file);
  590.                         } else {
  591.                             fclose($handle);
  592.                         }
  593.  
  594.                     } else {
  595.  
  596.                     }
  597.                 }
  598.                 // handle process timout
  599.                 if (isset($processes[$process_id]) && ($processes[$process_id]['started'] + $this->proc_time_out < time())) {
  600.                     $success = false;
  601.                     echo 'T';
  602.                     $num_echo++;
  603.                     unset($processes[$process_id]);
  604.                     $this->logError("Archive site process time outed: " . $process_id);
  605.                 }
  606.             }
  607.         } while (!empty($qs_batch) || !empty($processes));
  608.         echo "\n";
  609.         return $success;
  610.     }
  611.     /**
  612.     * @return bool True on success, false if some request failed
  613.     */
  614.     private function HTTP_archiveVisitsAndSegments($qs_batch, $urlNoSegment, $visitsAllDaysInPeriod) {
  615.         if (empty($qs_batch) || !is_array($qs_batch)) {
  616.             return false;
  617.         }
  618.         $aCurl = array();
  619.         $mh = curl_multi_init();
  620.         // mximum amount of // requests
  621.         $max_connect = 3;
  622.         $mh = curl_multi_init();
  623.         $success = true;
  624.         $visitsAllDaysInPeriod = false;
  625.         $running = null;
  626.         do {
  627.             $num_connect = 0;
  628.             // set up requests batch
  629.             foreach ($qs_batch as $k => $qs) {
  630.                 $_url = $this->piwikUrl . $qs . $this->requestPrepend;
  631.                 $ch = $this->getNewCurlHandle($_url);
  632.                 curl_multi_add_handle($mh, $ch);
  633.                 $aCurl[$_url] = $ch;
  634.                 unset($qs_batch[$k]);
  635.                 $this->requests++;
  636.                 $num_connect++;
  637.                 if ($num_connect >= $max_connect) {
  638.                     break;
  639.                 }
  640.             }
  641.             // exec them
  642.             $running=null;
  643.             do {
  644.                 usleep(10000);
  645.                 curl_multi_exec($mh, $running);
  646.             } while ($running > 0);
  647.             // get result
  648.             foreach($aCurl as $url => $ch){
  649.                 $content = curl_multi_getcontent($ch);
  650.                 curl_multi_remove_handle($mh, $ch);
  651.                 unset($aCurl[$url]);
  652.                 $successResponse = $this->checkResponse($content, $url);
  653.                 $success = $successResponse && $success;
  654.                 if ($url == $urlNoSegment && $successResponse) {
  655.                     $stats = unserialize($content);
  656.                     if (!is_array($stats)) {
  657.                         $this->logError("Error unserializing the following response: " . $content);
  658.                     }
  659.                     $visitsAllDaysInPeriod = @array_sum($stats);
  660.                 }
  661.             }
  662.         } while (!empty($qs_batch));
  663.         curl_multi_close($mh);
  664.         return $success;
  665.     }
  666.  
  667.  
  668.     public function list_dir($dir, $pattern = '', $with_dirs = false, $clearstats = true){
  669.         $files = array();
  670.         $dir = rtrim($dir, '/\\');
  671.         if ($clearstats) {
  672.             clearstatcache(true);
  673.         }
  674.         $dh = @opendir($dir);
  675.         if ($dh) {
  676.             while (($file = readdir($dh)) !== false) {
  677.                 if ($file !== '.' && $file !== '..') {
  678.                     if (!$with_dirs && is_dir("$dir/$file")) {
  679.                         continue;
  680.                     }
  681.                     if ($pattern && ! preg_match($pattern, $file)) {
  682.                         continue;
  683.                     }
  684.                     $files[] = $file;
  685.                 }
  686.             }
  687.             closedir($dh);
  688.         }
  689.         return $files;
  690.     }
  691.  
  692.     private function getNewCurlHandle($url)
  693.     {
  694.         $ch = curl_init($url);
  695.         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  696.  
  697.         if ($this->acceptInvalidSSLCertificate) {
  698.             curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  699.             curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  700.         }
  701.         curl_setopt($ch, CURLOPT_USERAGENT, Piwik_Http::getUserAgent());
  702.         Piwik_Http::configCurlCertificate($ch);
  703.         return $ch;
  704.     }
  705.  
  706.  
  707.     /**
  708.      * Logs a section in the output
  709.      */
  710.     private function logSection($title="")
  711.     {
  712.         $this->log("---------------------------");
  713.         $this->log($title);
  714.     }
  715.  
  716.     /**
  717.      * End of the script
  718.      */
  719.     public function end()
  720.     {
  721.         // How to test the error handling code?
  722.         // - Generate some hits since last archive.php run
  723.         // - Start the script, in the middle, shutdown apache, then restore
  724.         // Some errors should be logged and script should successfully finish and then report the errors and trigger a PHP error
  725.         if(!empty($this->errors))
  726.         {
  727.             $this->logSection("SUMMARY OF ERRORS");
  728.  
  729.             foreach($this->errors as $error) {
  730.                 $this->log("Error: ". $error);
  731.             }
  732.             $summary = count($this->errors) . " total errors during this script execution, please investigate and try and fix these errors";
  733.             $this->log($summary);
  734.  
  735.             $summary .= '. First error was: '. reset($this->errors);
  736.             $this->logFatalError($summary);
  737.         }
  738.         else
  739.         {
  740.             // No error -> Logs the successful script execution until completion
  741.             Piwik_SetOption(self::OPTION_ARCHIVING_FINISHED_TS, time());
  742.         }
  743.     }
  744.  
  745.  
  746.     private function log($m)
  747.     {
  748.         $this->output .= $m . "\n";
  749.         Piwik::log($m);
  750.     }
  751.  
  752.     /**
  753.      * Issues a request to $url
  754.      */
  755.     protected function request($url)
  756.     {
  757.         // Immlement full cli / http logic here as well
  758.         $url = $url . $this->requestPrepend;
  759.         //$this->log($url);
  760.         if ($this->archive_cli) {
  761.             $cmd = str_replace('\\', '/', $this->phpbin . ' ' . PW_ABS_PATH . 'index.php -- ' . trim($url, '?& '));
  762.             unset($out);
  763.             exec(escapeshellcmd($cmd), $out);
  764.             $response = implode("\n", $out);
  765.         } else {
  766.             try {
  767.                 $url = $this->piwikUrl. $url;
  768.                 $response = Piwik_Http::sendHttpRequestBy('curl', $url, $timeout = 300, $userAgent = null, $destinationPath = null, $file = null, $followDepth = 0, $acceptLanguage = false, $acceptInvalidSSLCertificate = $this->acceptInvalidSSLCertificate);
  769.             } catch(Exception $e) {
  770.                 return $this->logNetworkError($url, $e->getMessage());
  771.             }
  772.         }
  773.         if ($this->checkResponse($response, $url))
  774.         {
  775.             return $response;
  776.         }
  777.         return false;
  778.     }
  779.  
  780.     private function checkResponse($response, $url)
  781.     {
  782.         if(empty($response)
  783.             || stripos($response, 'error')) {
  784.             return $this->logNetworkError($url, $response);
  785.         }
  786.         return true;
  787.     }
  788.  
  789.     private function logError($m)
  790.     {
  791.         $this->errors[] = substr($m, 0, self::TRUNCATE_ERROR_MESSAGE_SUMMARY);
  792.         $this->log("ERROR: $m");
  793.     }
  794.  
  795.     public function logFatalError($m, $backtrace = true)
  796.     {
  797.         $this->logError($m);
  798.         $fe = fopen('php://stderr', 'w');
  799.         fwrite($fe, "Error in the last Piwik archive.php run: \n" . $m
  800.                 . ($backtrace ? "\n\n Here is the full errors output:\n\n" . $this->output : '')
  801.         );
  802.         trigger_error($m, E_USER_ERROR);
  803.         exit;
  804.     }
  805.  
  806.     private function logNetworkError($url, $response)
  807.     {
  808.         $message = "Got invalid response from API request: $url. ";
  809.         if(empty($response))
  810.         {
  811.             $message .= "The response was empty. This usually means a server error. This solution to this error is generally to increase the value of 'memory_limit' in your php.ini file. Please check your Web server Error Log file for more details.";
  812.         }
  813.         else
  814.         {
  815.             $message .= "Response was '$response'";
  816.         }
  817.         $this->logError($message);
  818.         return false;
  819.     }
  820.  
  821.     /**
  822.      * Displays script usage
  823.      */
  824.     protected function usage()
  825.     {
  826.         global $USAGE;
  827.         $this->logLines($USAGE);
  828.     }
  829.  
  830.     private function logLines($t)
  831.     {
  832.         foreach(explode(PHP_EOL, $t) as $line)
  833.         {
  834.             $this->log($line);
  835.         }
  836.     }
  837.  
  838.     private function initLog()
  839.     {
  840.         $config = Piwik_Config::getInstance();
  841.         $config->log['log_only_when_debug_parameter'] = 0;
  842.         $config->log['logger_message'] = array("logger_message" => "screen");
  843.         Piwik::createLogObject();
  844.         // curl not needed in cli mode
  845.         if(!$this->archive_cli && !function_exists("curl_multi_init")) {
  846.             $this->log("ERROR: this script requires curl extension php_curl enabled in your CLI php.ini");
  847.             $this->usage();
  848.             exit;
  849.         }
  850.     }
  851.  
  852.     /**
  853.      * Script does run on http:// ONLY if the SU token is specified
  854.      */
  855.     private function initCheckCli()
  856.     {
  857.         if(!Piwik_Common::isPhpCliMode())
  858.         {
  859.             $token_auth = Piwik_Common::getRequestVar('token_auth', '', 'string');
  860.             if($token_auth != $this->token_auth
  861.                 || strlen($token_auth) != 32)
  862.             {
  863.                 die('<b>You must specify the Super User token_auth as a parameter to this script, eg. <code>?token_auth=XYZ</code> if you wish to run this script through the browser. </b><br>
  864.                     However it is recommended to run it <a href="http://piwik.org/docs/setup-auto-archiving/">via cron in the command line</a>, since it can take a long time to run.<br/>
  865.                     In a shell, execute for example the following to trigger archiving on the local Piwik server:<br/>
  866.                     <code>$ /path/to/php /path/to/piwik/misc/cron/archive.php --url=http://your-website.org/path/to/piwik/</code>');
  867.             }
  868.         }
  869.     }
  870.  
  871.     /**
  872.      * Init Piwik, connect DB, create log & config objects, etc.
  873.      */
  874.     private function initCore()
  875.     {
  876.         try {
  877.             Piwik_FrontController::getInstance()->init();
  878.         } catch(Exception $e) {
  879.             echo "ERROR: During Piwik init, Message: ".$e->getMessage();
  880.             exit;
  881.         }
  882.     }
  883.  
  884.     private function displayHelp()
  885.     {
  886.         $displayHelp = $this->isParameterSet('help') || $this->isParameterSet('h');
  887.         if ($displayHelp)
  888.         {
  889.             $this->usage();
  890.             exit;
  891.         }
  892.     }
  893.  
  894.     protected function initStateFromParameters()
  895.     {
  896.         // Detect parameters
  897.         $reset = $this->isParameterSet("force-all-periods", $valuePossible = true);
  898.         $forceAll = $this->isParameterSet("force-all-websites");
  899.         $forceTimeoutPeriod = $this->isParameterSet("force-timeout-for-periods", $valuePossible = true);
  900.         if(!empty($forceTimeoutPeriod)
  901.             && $forceTimeoutPeriod !== true) // in case --force-timeout-for-periods= without [seconds] specified
  902.         {
  903.             // Ensure the cache for periods is at least as high as cache for today
  904.             $todayTTL = Piwik_ArchiveProcessing::getTodayArchiveTimeToLive();
  905.             if($forceTimeoutPeriod < $todayTTL)
  906.             {
  907.                 $this->log("WARNING: Automatically increasing --force-timeout-for-periods from $forceTimeoutPeriod to "
  908.                             . $todayTTL
  909.                             . " to match the cache timeout for Today's report specified in Piwik UI > Settings > General Settings");
  910.                 $forceTimeoutPeriod = $todayTTL;
  911.             }
  912.             $this->processPeriodsMaximumEverySeconds = $forceTimeoutPeriod;
  913.         }
  914.  
  915.         // Recommend to disable browser archiving when using this script
  916.         if( Piwik_ArchiveProcessing::isBrowserTriggerArchivingEnabled() )
  917.         {
  918.             $this->log("NOTE: if you execute this script at least once per hour (or more often) in a crontab, you may disable 'Browser trigger archiving' in Piwik UI > Settings > General Settings. ");
  919.             $this->log("      see doc at: http://piwik.org/docs/setup-auto-archiving/");
  920.         }
  921.  
  922.         if($reset)
  923.         {
  924.             $this->log("--force-all-periods was detected: the script will run as if it was its first run, and will trigger archiving for all periods.");
  925.             $this->shouldResetState = true;
  926.  
  927.             if(!$forceAll
  928.                 && is_numeric($reset)
  929.                 && $reset > 0)
  930.             {
  931.                 $this->firstRunActiveWebsitesWithTraffic = (int)$reset;
  932.             }
  933.         }
  934.  
  935.         if($forceAll)
  936.         {
  937.             $this->log("--force-all-websites was detected: the script will archive all websites and all periods sequentially");
  938.             $this->shouldArchiveAllWebsites = true;
  939.         }
  940.  
  941.         $this->timeLastCompleted = Piwik_GetOption(self::OPTION_ARCHIVING_FINISHED_TS);
  942.         if($this->shouldResetState)
  943.         {
  944.             $this->timeLastCompleted = false;
  945.         }
  946.     }
  947.  
  948.     // Fetching websites to process
  949.     protected function initWebsitesToProcess()
  950.     {
  951.         $this->allWebsites = Piwik_SitesManager_API::getInstance()->getAllSitesId();
  952.  
  953.         if($this->shouldArchiveAllWebsites)
  954.         {
  955.             $this->websites = $this->allWebsites;
  956.             $this->log("Will process ". count($this->websites). " websites");
  957.         }
  958.         else
  959.         {
  960.             // 1) All websites with visits since the last archive.php execution
  961.             $timestampActiveTraffic = $this->timeLastCompleted;
  962.             if(empty($timestampActiveTraffic))
  963.             {
  964.                 $timestampActiveTraffic = time() - $this->firstRunActiveWebsitesWithTraffic;
  965.                 $this->log("--force-all-periods was detected: we will process websites with visits in the last "
  966.                         . Piwik::getPrettyTimeFromSeconds($this->firstRunActiveWebsitesWithTraffic, true, false)
  967.                 );
  968.             }
  969.             $this->websites = Piwik_SitesManager_API::getInstance()->getSitesIdWithVisits( $timestampActiveTraffic );
  970.             $websiteIds = !empty($this->websites) ? ", IDs: ".implode(", ", $this->websites) : "";
  971.             $prettySeconds = Piwik::getPrettyTimeFromSeconds(   empty($this->timeLastCompleted)
  972.                                                                     ? $this->firstRunActiveWebsitesWithTraffic
  973.                                                                     : (time() - $this->timeLastCompleted),
  974.                                                                 true, false);
  975.             $this->log("Will process ". count($this->websites). " websites with new visits since "
  976.                             . $prettySeconds
  977.                             . " "
  978.                             . $websiteIds);
  979.  
  980.             // 2) All websites that had reports in the past invalidated recently
  981.             //  eg. when using Python log import script
  982.             $this->idSitesInvalidatedOldReports = Piwik_CoreAdminHome_API::getWebsiteIdsToInvalidate();
  983.             $this->idSitesInvalidatedOldReports = array_intersect($this->idSitesInvalidatedOldReports, $this->allWebsites);
  984.  
  985.             if(count($this->idSitesInvalidatedOldReports) > 0)
  986.             {
  987.                 $websiteIds = ", IDs: ".implode(", ", $this->idSitesInvalidatedOldReports);
  988.                 $this->log("Will process ". count($this->idSitesInvalidatedOldReports). " other websites because some old data reports have been invalidated (eg. using the Log Import script) " . $websiteIds);
  989.                 $this->websites = array_merge($this->websites, $this->idSitesInvalidatedOldReports);
  990.             }
  991.  
  992.             // 3) Also process all other websites which days have finished since the last run.
  993.             //    This ensures we process the previous day/week/month/year that just finished, even if there was no new visit
  994.             $uniqueTimezones = Piwik_SitesManager_API::getInstance()->getUniqueSiteTimezones();
  995.             $timezoneToProcess = array();
  996.             foreach($uniqueTimezones as &$timezone)
  997.             {
  998.                 $processedDateInTz = Piwik_Date::factory((int)$timestampActiveTraffic, $timezone);
  999.                 $currentDateInTz = Piwik_Date::factory('now', $timezone);
  1000.  
  1001.                 if($processedDateInTz->toString() != $currentDateInTz->toString() )
  1002.                 {
  1003.                     $timezoneToProcess[] = $timezone;
  1004.                 }
  1005.             }
  1006.  
  1007.             $websiteDayHasFinishedSinceLastRun = Piwik_SitesManager_API::getInstance()->getSitesIdFromTimezones($timezoneToProcess);
  1008.             $websiteDayHasFinishedSinceLastRun = array_diff($websiteDayHasFinishedSinceLastRun, $this->websites);
  1009.             $this->websiteDayHasFinishedSinceLastRun = $websiteDayHasFinishedSinceLastRun;
  1010.             if(count($websiteDayHasFinishedSinceLastRun) > 0)
  1011.             {
  1012.                 $websiteIds = !empty($websiteDayHasFinishedSinceLastRun) ? ", IDs: ".implode(", ", $websiteDayHasFinishedSinceLastRun) : "";
  1013.                 $this->log("Will process ". count($websiteDayHasFinishedSinceLastRun). " other websites because the last time they were archived was on a different day (in the website's timezone) " . $websiteIds);
  1014.  
  1015.                 $this->websites = array_merge($this->websites, $websiteDayHasFinishedSinceLastRun);
  1016.             }
  1017.         }
  1018.     }
  1019.  
  1020.     protected function initTokenAuth()
  1021.     {
  1022.         $login = Piwik_Config::getInstance()->superuser['login'];
  1023.         $md5Password = Piwik_Config::getInstance()->superuser['password'];
  1024.         $this->token_auth = md5( $login . $md5Password ) ;
  1025.         $this->login = $login;
  1026.     }
  1027.  
  1028.     private function initPiwikHost()
  1029.     {
  1030.         // If archive.php run as a web cron, we use the current hostname
  1031.         if(!Piwik_Common::isPhpCliMode())
  1032.         {
  1033.             // example.org/piwik/misc/cron/
  1034.             $piwikUrl = Piwik_Common::sanitizeInputValue(Piwik_Url::getCurrentUrlWithoutFileName());
  1035.             // example.org/piwik/
  1036.             $piwikUrl = $piwikUrl . "../../";
  1037.         }
  1038.         // If archive.php run as CLI/shell we require the piwik url to be set
  1039.         else
  1040.         {
  1041.             $piwikUrl = $this->isParameterSet("url", true);
  1042.             if(!$piwikUrl
  1043.                 || !Piwik_Common::isLookLikeUrl($piwikUrl))
  1044.             {
  1045.                 $this->logFatalError("archive.php expects the argument --url to be set to your Piwik URL, for example: --url=http://example.org/piwik/ ", $backtrace = false);
  1046.             }
  1047.             // ensure there is a trailing slash
  1048.             if($piwikUrl[strlen($piwikUrl)-1] != '/')
  1049.             {
  1050.                 $piwikUrl .= '/';
  1051.             }
  1052.         }
  1053.         if(Piwik_Config::getInstance()->General['force_ssl'] == 1)
  1054.         {
  1055.             $piwikUrl = str_replace('http://', 'https://', $piwikUrl);
  1056.         }
  1057.         $this->piwikUrl = $piwikUrl . "index.php";
  1058.     }
  1059.  
  1060.  
  1061.     /**
  1062.      * Returns if the requested parameter is defined in the command line arguments.
  1063.      * If $valuePossible is true, then a value is possibly set for this parameter,
  1064.      * ie. --force-timeout-for-periods=3600 would return 3600
  1065.      *
  1066.      * @return true or the value (int,string) if set, false otherwise
  1067.      */
  1068.     private function isParameterSet($parameter, $valuePossible = false)
  1069.     {
  1070.         if(!Piwik_Common::isPhpCliMode())
  1071.         {
  1072.             return false;
  1073.         }
  1074.         $parameters = array(
  1075.             "--$parameter",
  1076.             "-$parameter",
  1077.             $parameter
  1078.         );
  1079.         foreach($parameters as $parameter)
  1080.         {
  1081.             foreach($_SERVER['argv'] as $arg)
  1082.             {
  1083.                 if( strpos($arg, $parameter) === 0)
  1084.                 {
  1085.                     if($valuePossible)
  1086.                     {
  1087.                         $parameterFound = $arg;
  1088.                         if(($posEqual = strpos($parameterFound, '=')) !== false)
  1089.                         {
  1090.                             $return = substr($parameterFound, $posEqual+1);
  1091.                             if($return !== false)
  1092.                             {
  1093.                                 return $return;
  1094.                             }
  1095.                         }
  1096.                     }
  1097.                     return true;
  1098.                 }
  1099.             }
  1100.         }
  1101.         return false;
  1102.     }
  1103.  
  1104. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement