<?php

/**
 * Defines the Dashboard interface for the CMS
 *
 * @package Dashboard
 * @author Uncle Cheese <unclecheese@leftandmain.com>
 */
class Dashboard extends LeftAndMain implements PermissionProvider {

	private static $menu_title = "Dashboard";

	private static $url_segment = "dashboard";

	private static $menu_priority = 100;

	private static $url_priority = 30;

	private static $menu_icon = "dashboard/images/dashboard.png";

	private static $tree_class = 'DashboardPanel';

	private static $url_handlers = array (

		'panel/$ID' => 'handlePanel',
		'$Action!' => '$Action',
		'' => 'index'
	);

	public function init() {
		parent::init();
		Requirements::css("dashboard/css/dashboard.css");
		Requirements::javascript("dashboard/javascript/jquery.flip.js");
		Requirements::javascript("dashboard/javascript/dashboard.js");
	}

	private static $allowed_actions = array(
		"handlePanel",
		"sort",
		"setdefault",
		"applytoall"
	);

	/**
	 * Provides custom permissions to the Security section
	 *
	 * @return array
	 */
	public function providePermissions() {
		$title = _t("Dashboard.MENUTITLE", LeftAndMain::menu_title_for_class('Dashboard'));
		return array(
			"CMS_ACCESS_Dashboard" => array(
				'name' => _t('Dashboard.ACCESS', "Access to '{title}' section", array('title' => $title)),
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
				'help' => _t(
					'Dashboard.ACCESS_HELP',
					'Allow use of the CMS Dashboard'
				)
			),
			"CMS_ACCESS_DashboardAddPanels" => array(
				'name' => _t('Dashboard.ADDPANELS', "Add dashboard panels"),
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
				'help' => _t(
					'Dashboard.ACCESS_HELP',
					'Allow user to add panels to his/her dashboard'
				)
			),
			"CMS_ACCESS_DashboardConfigurePanels" => array(
				'name' => _t('Dashboard.CONFIGUREANELS', "Configure dashboard panels"),
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
				'help' => _t(
					'Dashboard.ACCESS_HELP',
					'Allow user to configure his/her dashboard panels'
				),
			),
			"CMS_ACCESS_DashboardDeletePanels" => array(
				'name' => _t('Dashboard.DELETEPANELS', "Remove dashboard panels"),
				'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access'),
				'help' => _t(
					'Dashboard.ACCESS_HELP',
					'Allow user to remove panels from his/her dashboard'
				)
			)
		);
	}

	/**
	 * Handles a request for a {@link DashboardPanel} object. Can be a new record or existing
	 *
	 * @param SS_HTTPRequest The current request
	 * @return SS_HTTPResponse
	 */
	public function handlePanel(SS_HTTPRequest $r) {
		if($r->param('ID') == "new") {
			$class = $r->getVar('type');
			if($class && class_exists($class) && is_subclass_of($class, "DashboardPanel")) {
				$panel = new $class();
				if($panel->canCreate()) {
					$panel->MemberID = Member::currentUserID();
					$panel->Title = $panel->getLabel();
					$panel->write();
				}
				else {
					$panel = null;
				}
			}
		}
		else {
			$panel = DashboardPanel::get()->byID((int) $r->param('ID'));
		}
		if($panel && ($panel->canEdit() || $panel->canView())) {
			$requestClass = $panel->getRequestHandlerClass();
			$handler = Object::create($requestClass, $this, $panel);
			return $handler->handleRequest($r, DataModel::inst());

		}
		return $this->httpError(404);
	}

	/**
	 * A controller action that handles the reordering of the panels
	 *
	 * @param SS_HTTPRequest The current request
	 * @return SS_HTTPResponse
	 */
	public function sort(SS_HTTPRequest $r) {
		if($sort = $r->requestVar('dashboard-panel')) {
			foreach($sort as $index => $id) {
				if($panel = DashboardPanel::get()->byID((int) $id)) {
					if($panel->MemberID == Member::currentUserID()) {
						$panel->SortOrder = $index;
						$panel->write();
					}
				}
			}
		}
	}

	/**
	 * A controller action that handles setting the default dashboard configuration
	 *
	 * @param SS_HTTPRequest The current request
	 * @return SS_HTTPResponse
	 */
	public function setdefault(SS_HTTPRequest $r) {
		foreach(SiteConfig::current_site_config()->DashboardPanels() as $panel) {
			$panel->delete();
		}
		foreach(Member::currentUser()->DashboardPanels() as $panel) {
			$clone = $panel->duplicate();
			$clone->MemberID = 0;
			$clone->SiteConfigID = SiteConfig::current_site_config()->ID;
			$clone->write();
		}
		return new SS_HTTPResponse(_t('Dashboard.SETASDEFAULTSUCCESS','Success! This dashboard configuration has been set as the default for all new members.'));
	}

	/**
	 * A controller action that handles the application of a dashboard configuration to all members
	 *
	 * @param SS_HTTPRequest The current request
	 * @return SS_HTTPResponse
	 */
	public function applytoall(SS_HTTPRequest $r) {
		$members = Permission::get_members_by_permission(array("CMS_ACCESS_Dashboard","ADMIN"));
		foreach($members as $member) {
			if($member->ID == Member::currentUserID()) continue;

			$member->DashboardPanels()->removeAll();
			foreach(Member::currentUser()->DashboardPanels() as $panel) {
				$clone = $panel->duplicate();
				$clone->MemberID = $member->ID;
				$clone->write();
			}
		}
		return new SS_HTTPResponse(_t('Dashboard.APPLYTOALLSUCCESS','Success! This dashboard configuration has been applied to all members who have dashboard access.'));
	}

	/**
	 * Gets the current user's dashboard configuration
	 *
	 * @return DataList
	 */
	public function BasePanels() {
		return Member::currentUser()->DashboardPanels();
	}

	/**
	 * Gets the current user's dashboard configuration
	 *
	 * @return DataList
	 */
	public function Panels() {
		return Member::currentUser()->DashboardPanels();
	}

	/**
	 * Gets all the available panels that can be installed on the dashboard. All subclasses of
	 * {@link DashboardPanel} are included
	 *
	 * @return ArrayList
	 */
	public function AllPanels() {
		$set = ArrayList::create(array());
		$panels = SS_ClassLoader::instance()->getManifest()->getDescendantsOf("DashboardPanel");
		if($this->config()->excluded_panels) {
			$panels = array_diff($panels,$this->config()->excluded_panels);
		}
		foreach($panels as $class) {
			$SNG = Injector::inst()->get($class);
			$SNG->Priority = Config::inst()->get($class, "priority", Config::INHERITED);
			if($SNG->registered() == true){
				$set->push($SNG);
			}
		}
		return $set->sort("Priority");
	}

	/**
	 * A template accessor to check the ADMIN permission
	 *
	 * @return bool
	 */
	public function IsAdmin() {
		return Permission::check("ADMIN");
	}

	/**
	 * Check the permission to make sure the current user has a dashboard
	 *
	 * @return bool
	 */
	public function canView($member = null) {
		return Permission::check("CMS_ACCESS_Dashboard");
	}

	/**
	 * Check if the current user can add panels to the dashboard
	 *
	 * @return bool
	 */
	public function CanAddPanels() {
		return Permission::check("CMS_ACCESS_DashboardAddPanels");
	}

	/**
	 * Check if the current user can delete panels from the dashboard
	 *
	 * @return bool
	 */
	public function CanDeletePanels() {
		return Permission::check("CMS_ACCESS_DashboardDeletePanels");
	}

	/**
	 * Check if the current user can configure panels on the dashboard
	 *
	 * @return bool
	 */
	public function CanConfigurePanels() {
		return Permission::check("CMS_ACCESS_DashboardConfigurePanels");
	}

}

/**
 * Defines the {@link RequestHandler} object that is responsible for rendering dashboard panels
 * and processing their input.
 *
 * @package Dashboard
 * @author Uncle Cheese <unclecheese@leftandmain.com>
 */
class Dashboard_PanelRequest extends RequestHandler {

	private static $url_handlers = array (
		'$Action!' => '$Action',
		'' => 'panel'

	);

	private static $allowed_actions = array(
		"panel",
		"delete",
		"ConfigureForm",
		"saveConfiguration"
	);

	protected $dashboard;

	protected $panel;

	public function __construct(Dashboard $dashboard, DashboardPanel $panel) {
		$this->dashboard = $dashboard;
		$this->panel = $panel;
		parent::__construct();
	}

	/**
	 * Gets the link to this request. Useful for rendering the nested Form. Also provides an easy
	 * "refresh" link to the panel that is managed by this request
	 *
	 * @return string
	 */
	public function Link() {
		return $this->dashboard->Link("panel/{$this->panel->ID}");
	}

	/**
	 * Renders the panel in this request
	 *
	 * @param SS_HTTPRequest
	 * @return SSViewer
	 */
	public function panel(SS_HTTPRequest $r) {
		if($this->panel->canView()) {
			return $this->panel->PanelHolder();
		}
		return $this->httpError(403);
	}

	/**
	 * Delets the panel in this request
	 *
	 * @param SS_HTTPRequest
	 * @return SS_HTTPResponse
	 */
	public function delete(SS_HTTPRequest $r) {
		if($this->panel->canDelete()) {
			$this->panel->delete();
			return new SS_HTTPResponse("OK");
		}
	}

	/**
	 * Gets the configuration form for this panel and handles the form input
	 *
	 * @return Form
	 */
	public function ConfigureForm() {
		$form = Form::create(
			$this,
			"ConfigureForm",
			$this->panel->getConfiguration(),
			FieldList::create(
				FormAction::create("saveConfiguration",_t('Dashboard.SAVE','Save'))
					->setUseButtonTag(true)
					->addExtraClass('ss-ui-action-constructive'),
				FormAction::create("cancel",_t('Dashboard.CANCEL','Cancel'))
					->setUseButtonTag(true)
			)
		);
		$form->loadDataFrom($this->panel);
		$form->setHTMLID("Form_ConfigureForm_".$this->panel->ID);
		$form->addExtraClass("configure-form");
		return $form;
	}

	/**
	 * Processes the form input and writes the panel
	 *
	 * @param array The raw POST data from the form
	 * @param Form The ConfigurationForm
	 * @return SS_HTTPResponse
	 */
	public function saveConfiguration($data, $form) {
		$panel = $this->panel;
		$form->saveInto($panel);
		$panel->write();
	}

}


<?php

/**
 * Defines the "Section Editor" dashboard panel type
 *
 * @package Dashboard
 * @author Uncle Cheese <unclecheese@leftandmain.com>
 */
class DashboardSectionEditorPanel extends DashboardPanel {

	private static $db = array (
		'ParentID' => 'Int',
		'Subject' => 'Varchar',
		'Count' => 'Int'
	);

	private static $defaults = array (
		'Count' => 10
	);

	private static $configure_on_create = true;

	public function getLabel() {
		return _t('Dashboard.SECTIONEDTIORLABEL','Section Editor');
	}

	public function getDescription() {
		return _t('Dashbaord.SECTIONEDITORDESCRIPTION','Pulls pages from a section of the website for viewing and creation');
	}

	/**
	 * Gets the icon for the panel. Use the icon of the subject page when possible, otherwise
	 * fall back on a default icon.
	 *
	 * @return string
	 */
	public function Icon() {
		$s = $this->Subject ? $this->Subject : "SiteTree";
		$file = Config::inst()->get($s, "icon", Config::INHERITED).".png";
		if(!Director::fileExists($file)) {
			$file = Config::inst()->get($s, "icon", Config::INHERITED)."-file.gif";
		}
		if(!Director::fileExists($file)) {
			$file = "dashboard/images/section-editor.png";
		}

		return $file;
	}

	/**
	 * A recursive function get gets the hierarchy of the SiteTree that use usable in a {@link DropdownField}.
	 * {@link TreeDropdownField} cannot be used due to UI constraints.
	 *
	 * @param int The ID of the root node
	 * @param int Level the current depth of the tree
	 * @return array
	 */
	protected function getHierarchy($parentID, $level = 0) {
		$options = array();
		$class = "SiteTree";
		$filter = array('ParentID' => $parentID);
		$children = DataList::create($class)->filter($filter);
		if($children->exists()) {
			foreach($children as $child) {
				$indent="";
				for($i=0;$i<$level;$i++) $indent .= "  ";
				if($child->AllChildren()->exists()) {
					$text = $child->Title;
					$options[$child->ID] = empty($text) ? "<em>$indent Untitled</em>" : $indent.$text;
				}
				$options += $this->getHierarchy($child->ID, $level+1);
			}
		}
		return $options;
	}

	/**
	 * Get a list of all page types available in the CMS
	 *
	 * @return array
	 */
	protected function getPageTypes() {
		$types = array();
		foreach(SS_ClassLoader::instance()->getManifest()->getDescendantsOf("SiteTree") as $class) {
			$types[$class] = $class;
		}
		return $types;
	}

	public function getConfiguration() {
		$fields = parent::getConfiguration();
		$fields->push(TreeDropdownField::create("ParentID","Section","SiteTree")
			->setFilterFunction(function($node){
				return SiteTree::get()->filter("ParentID", $node->ID)->exists();
			})
		);
		$fields->push(DropdownField::create("Subject", "Page type", $this->getPageTypes())
			->addExtraClass("no-chzn")
		);
		$fields->push(TextField::create("Count",_t('DashboardRecentEdits.COUNT','Number of pages to display')));
		return $fields;
	}

	public function getPrimaryActions() {
		if(!$this->Subject || !$this->ParentID) return false;
		$actions = parent::getPrimaryActions();
		$actions->push(DashboardPanelAction::create(
			$this->CreatePageLink(),
			sprintf(_t('Dashboard.CREATENEW','Create new %s'),$this->SubjectSingularName()),
			"good"
		));
		return $actions;
	}

	public function getSecondaryActions() {
		if(!$this->Subject || !$this->ParentID) return false;
		$actions = parent::getSecondaryActions();
		$actions->push(DashboardPanelAction::create(
			$this->ViewAllLink(),
			sprintf(_t('Dashboard.VIEWALL','View all %s'),$this->SubjectPluralName())
		));
		return $actions;

	}

	public function SectionItems() {
		if(!$this->Subject || !$this->ParentID) return false;
		$set = SiteTree::get()
			->filter(array(
				'ParentID' => $this->ParentID,
				'ClassName' => $this->Subject
			))
			->limit($this->Count)
			->sort("LastEdited DESC");
		$ret = ArrayList::create(array());
		foreach($set as $r) {
			$ret->push(ArrayData::create(array(
				'Title' => $r->Title,
				'EditLink' => Injector::inst()->get("CMSPagesController")->Link("edit/show/{$r->ID}")
			)));
		}
		return $ret;
	}

	public function SubjectSingularName() {
		return Injector::inst()->get($this->Subject)->i18n_singular_name();
	}

	public function SubjectPluralName() {
		return Injector::inst()->get($this->Subject)->i18n_plural_name();
	}

	public function CreatePageLink() {
		return Injector::inst()->get("CMSPagesController")->Link("add/AddForm")."?action_doAdd=1&ParentID={$this->ParentID}&PageType={$this->Subject}&SecurityID=".SecurityToken::getSecurityID();
	}

	public function ViewAllLink() {
		return Injector::inst()->get("CMSPagesController")->Link()."?ParentID=$this->ParentID&view=list";
	}

}


<?php

/**
 * Defines the DashboardPanel type that shows Google Analytics data
 *
 * @package Dashboard
 * @author Uncle Cheese <unclecheese@leftandmain.com>
 */
class DashboardGoogleAnalyticsPanel extends DashboardPanel {

	private static $db = array (
		'DateFormat' => "Enum('mdy,dmy','dmy')",
		'DateRange' => "Enum('day,week,month,year','month')",
		'PathType' => "Enum('none,list,custom','none')",
		'CustomPath' => 'Varchar'
	);

	private static $has_one = array (
		'SubjectPage' => 'SiteTree'
	);

	private static $icon = "dashboard/images/google-analytics.png";

	/**
	 * @var gapi A stored instantiation of the Google Analytics API object
	 */
	protected $gapi;

	/**
	 * Stores any exceptions coming from GAPI for easy rendering on the template
	 * @var string
	 */
	protected $error;

	/**
	 * A factory method that converts seconds to minutes. GA works in seconds.
	 *
	 * @param int The number of seconds
	 * @return string A human readable time value of minutes:seconds
	 */
	public static function seconds_to_minutes($seconds) {
	    $minResult = floor($seconds/60);
	    if($minResult < 10){$minResult = 0 . $minResult;}
	    $secResult = ($seconds/60 - $minResult)*60;
	    if($secResult < 10){$secResult = 0 . round($secResult);}
	    else { $secResult = round($secResult); }
	    return $minResult.":".$secResult;
	}

	/**
	 * DEPRECATED: Sets the username and password for the Google Analytics account
	 */
	public static function set_account($email, $password, $profile) {
		throw new Exception("DashboardGoogleAnalyticsPanel::set_account() is no longer supported. Use the config settings 'email', 'profile', and 'key_file_path' in the DashboardGoogleAnalyticsPanel config. More info on oauth key: https://github.com/erebusnz/gapi-google-analytics-php-interface#instructions-for-setting-up-a-google-service-account-for-use-with-gapi");
	}

	/**
	 * Gets the label for this panel
	 *
	 * @return string
	 */
	public function getLabel() {
		return "Google Analytics";
	}

	public function getError() {
		return $this->error;
	}

	/**
	 * Gets the title for this panel
	 *
	 * @return string
	 */
	public function getDescription() {
		return _t('Dashboard.GOOGLEDESCRIPTION','Displays a Google Analytics chart for a given page');
	}

	/**
	 * Gets the GAPI object. Creates if necessary.
	 *
	 * @return gapi
	 */
	public function api() {
		if(!$this->gapi) {
			try {
				$this->gapi = new gapi(
					self::config()->email,
					Director::getAbsFile(self::config()->key_file_path)
				);
			}
			catch(Exception $e) {
				$this->error = $e->getMessage();

				return $this->gapi;
			}
		}
		return $this->gapi;
	}

	/**
	 * Gets the configuration FieldList for this panel
	 *
	 * @return FieldList
	 */
	public function getConfiguration() {
		$fields = parent::getConfiguration();
		if(!$this->isValid()) {
			$fields->push(LiteralField::create("warning",_t('Dashboard.NOGOOGLEACCOUNT','
You have not configured a valid Google Analytics account.
')));
			return $fields;

		}

		$pages = $this->getHierarchy(0);
		$fields->push(OptionsetField::create("PathType",_t('Dashboard.FILTERBYPAGE','Filter'),array(
				'none' => _t('Dashboard.NONESHOWALL','No filter. Show analytics for the entire site'),
				'list' => _t('Dashboard.PAGEINLIST','Filter by a specific page in the tree'),
				'custom' => _t('Dashboard.ACUSTOMPATH','Filter by a specific path')
			)));
		$fields->push(DropdownField::create("SubjectPageID",'',$pages)
			->addExtraClass('no-chzn')
			->setEmptyString("-- " . _t('Dashboard.PLEASESELECT','Please select')." --")
		);
		$fields->push(TextField::create("CustomPath", '')
			->setAttribute('placeholder','e.g. /about-us/contact')
		);
		$fields->push(DropdownField::create("DateFormat",_t('Dashboard.DATEFORMAT','Date format'),array(
				'dmy' => date('j M, Y'),
				'mdy' => date('M j, Y')
			))
			->addExtraClass('no-chzn')
		);
		$fields->push(DropdownField::create("DateRange", _t('Dashboard.DATERANGE','Date range'),array(
				'day' => _t('Dashboard.TODAY','Today'),
				'week' => _t('Dashboard.PREVIOUSSEVENDAYS','7 days'),
				'month' => _t('Dashboard.PREVIOUSTHIRTYDAYS','30 days'),
				'year' => _t('Dashboard.PREVIOUSYEAR','365 days')
			))
			->addExtraClass('no-chzn')
		);
		return $fields;

	}

	/**
	 * A recursive function that gets a hierarchy of the site tree for a dropdown. Cannot use
	 * the {@link TreeDropdownField} here due to UI constraints.
	 *
	 * @param int The ID of the root node of the tree
	 * @param int The level of depth in the hierarchy. Used to create a visial hierarchy.
	 * @return array
	 */
	protected function getHierarchy($parentID, $level = 0) {
		$options = array();
		$class = "SiteTree";
		$filter = array('ParentID' => $parentID);
		$children = DataList::create($class)->filter($filter);
		if($children->exists()) {
			foreach($children as $child) {
				$indent="";
				for($i=0;$i<$level;$i++) $indent .= "  ";
				$text = $child->Title;
				$options[$child->ID] = empty($text) ? "$indent Untitled" : $indent.$text;
				$options += $this->getHierarchy($child->ID, $level+1);
			}
		}
		return $options;
	}

	/**
	 * Prevents the chart from rendering if critical values are not set
	 *
	 * @return bool
	 */
	public function isValid() {
		return $this->IsConfigured() && $this->IsConnected();
	}

	/**
	 * A template accessor to determine if the connection to the API was successful
	 *
	 * @return bool
	 */
	public function IsConnected() {
		return $this->api() ? true : false;
	}

	/**
	 * A template access to determine if the credentials have been entered
	 *
	 * @return bool
	 */
	public function IsConfigured() {
		$c = self::config();

		return (
			$c->email &&
			$c->key_file_path &&
			$c->profile &&
			Director::fileExists($c->key_file_path)
		);
	}

	/**
	 * Gets a timestamp for the start date of reporting, based on user provided data
	 *
	 * @return int
	 */
	public function getStartDateStamp() {
		switch($this->DateRange) {
			case "day":
				return time();
			case "week":
				return strtotime("-7 days");
			case "month":
				return strtotime("-30 days");
			case "year":
				return strtotime("-1 year");
			default:
				return strtotime("-30 days");

		}
	}

	/**
	 * Gets the path to the subject page of the analytics. Can be a native SiteTree object or a custom path
	 *
	 * @return string
	 */
	public function getPath() {
		if($this->PathType == "list") {
			return $this->SubjectPage()->exists() ? $this->SubjectPage()->Link() : "/";
		}
		elseif($this->PathType == "custom") {
			return $this->CustomPath;
		}
	}

	/**
	 * Loads the requirements before rendering the panel.
	 *
	 * @return SSViewer
	 */
	public function PanelHolder() {
		Requirements::javascript("dashboard/javascript/dashboard-google-analytics.js");
		Requirements::css("dashboard/css/dashboard-google-analytics.css");
		return parent::PanelHolder();
	}

	/**
	 * Gets the title of the chart. A composite of date range and path
	 *
	 * @return string
	 */
	public function ChartTitle() {
		$stamp = $this->getStartDateStamp();
		$key = $this->DateFormat == "dmy" ? "j M, Y" : "M j, Y";
		$title = date($key, $stamp) . " - " . date($key);
		if($this->getPath()) {
			$title .= " ("._t('Dashboard.PATH','Path').": {$this->getPath()})";
		}
		else {
			$title .= " ("._t('Dashboard.ENTIRESITE','Entire site').")";
		}
		return $title;

	}

	/**
	 * Gets the result set for date/pageview pairs
	 *
	 * @return ArrayList
	 */
	public function Chart() {
		if(!$this->isValid()) return false;
		try {
			$this->api()->requestReportData(
				self::config()->profile,
				array('date'),
				array('pageviews'),
				'date',
				$this->getPath() ? "pagePath == {$this->getPath()}" : null,
				date('Y-m-d',$this->getStartDateStamp())
			);
		}
		catch(Exception $e) {
			$this->error = $e->getMessage();

			return false;
		}

		$results = $this->api()->getResults();

		if($results) {
			$chart = DashboardChart::create(
				$this->ChartTitle(),
				_t('Dashboard.DAY','Day'),
				_t('Dashboard.PAGEVIEWS','Pageviews')
			);
			$chart->Height = 160;
			$datekey = $this->DateFormat == "mdy" ? "M j" : "j M";
			if($this->PathType == "none") {
				$map = array ();
				foreach($results as $result) {
					$date = date($datekey, strtotime($result->getDate()));
					if(!isset($map[$date])) $map[$date] = 0;
					$map[$date] += $result->getPageViews();
				}
				$chart->setData($map);
			}
			else {
				foreach($results as $result) {
					$data = date($datekey, strtotime($result->getDate()));
					$chart->addData($data, $result->getPageViews());
				}
			}
			return $chart;
		}
		return false;
	}

	/**
	 * Gets a result set of aggregate values for the given date range, e.g. total pageviews.
	 *
	 * @return ArrayList
	 */
	public function PageResults() {
		if(!$this->isValid()) return false;
		try {
			$this->api()->requestReportData(
				self::config()->profile,
				'pagePath',
				array('pageviews', 'uniquePageviews', 'exitRate', 'avgTimeOnPage', 'entranceBounceRate'),
				null,
				$this->getPath() ? "pagePath == {$this->getPath()}" : null,
				date('Y-m-d',$this->getStartDateStamp())
			);
		}
		catch(Exception $e) {
			$this->error = $e->getMessage();

			return false;
		}
		$set = ArrayList::create(array());
		if(!$this->getPath()) {
			$metrics = $this->api()->getMetrics();
			if($metrics) {
				$set->push(ArrayData::create(array(
					'FormattedPageViews' => number_format($metrics['pageviews']),
					'FormattedUniquePageViews' => number_format($metrics['uniquePageviews']),
					'AverageMinutesOnPage' => self::seconds_to_minutes($metrics['avgTimeOnPage']),
					'BounceRate' => round($metrics['entranceBounceRate'],2)."%",
					'ExitRate' => round($metrics['exitRate'],2)."%"
				)));
			}
			return $set;
		}
		$results = $this->api()->getResults();
		if($results) {
			foreach($results as $result) {
				$set->push(ArrayData::create(array(
					'FormattedPageViews' => number_format($result->getPageViews()),
					'FormattedUniquePageViews' => number_format($result->getUniquePageViews()),
					'AverageMinutesOnPage' => self::seconds_to_minutes($result->getAvgtimeonpage()),
					'BounceRate' => round($result->getEntrancebouncerate(), 2).'%',
					'ExitRate' => round($result->getExitrate(), 2).'%'
				)));
			}

		}
		return $set;

	}

}


<?php

namespace PhpBinaryReader\Type;

use PhpBinaryReader\BinaryReader;

class Single implements TypeInterface
{
    /**
     * Returns a 4-bytes floating-point
     *
     * @param \PhpBinaryReader\BinaryReader $br
     * @param null $length
     *
     * @return float
     * @throws \OutOfBoundsException
     */
    public function read(BinaryReader &$br, $length = null)
    {
        if (!$br->canReadBytes(4)) {
            throw new \OutOfBoundsException('Cannot read 4-bytes floating-point, it exceeds the boundary of the file');
        }

        $segment = $br->readFromHandle(4);

        if ($br->getCurrentBit() !== 0) {
            $data = unpack('N', $segment)[1];
            $data = $this->bitReader($br, $data);

            $endian = $br->getMachineByteOrder() === $br->getEndian() ? 'N' : 'V';
            $segment = pack($endian, $data);
        } elseif ($br->getMachineByteOrder() !== $br->getEndian()) {
            $segment = pack('N', unpack('V', $segment)[1]);
        }

        $value = unpack('f', $segment)[1];

        return $value;
    }

    /**
     * @param \PhpBinaryReader\BinaryReader $br
     * @param int $data
     *
     * @return int
     */
    private function bitReader(BinaryReader $br, $data)
    {
        $mask = 0x7FFFFFFF >> ($br->getCurrentBit() - 1);
        $value = (($data >> (8 - $br->getCurrentBit())) & $mask) | ($br->getNextByte() << (24 + $br->getCurrentBit()));
        $br->setNextByte($data & 0xFF);

        return $value;
    }
}


<?php

namespace PhpBinaryReader\Type;

use PhpBinaryReader\BinaryReader;
use PhpBinaryReader\BitMask;

class Int8 implements TypeInterface
{
    /**
     * @var string
     */
    private $endian = 'C';

    /**
     * Returns an Unsigned 8-bit Integer (aka a single byte)
     *
     * @param  \PhpBinaryReader\BinaryReader $br
     * @param  null                          $length
     * @return int
     * @throws \OutOfBoundsException
     */
    public function read(BinaryReader &$br, $length = null)
    {
        if (!$br->canReadBytes(1)) {
            throw new \OutOfBoundsException('Cannot read 8-bit int, it exceeds the boundary of the file');
        }

        $segment = $br->readFromHandle(1);

        $data = unpack($this->endian, $segment);
        $data = $data[1];

        if ($br->getCurrentBit() != 0) {
            $data = $this->bitReader($br, $data);
        }

        return $data;
    }

    /**
     * Returns a Signed 8-bit Integer (aka a single byte)
     *
     * @param  \PhpBinaryReader\BinaryReader $br
     * @return int
     */
    public function readSigned(&$br)
    {
        $this->setEndian('c');
        $value = $this->read($br);
        $this->setEndian('C');

        return $value;
    }

    /**
     * @param  \PhpBinaryReader\BinaryReader $br
     * @param  int                           $data
     * @return int
     */
    private function bitReader(&$br, $data)
    {
        $bitmask = new BitMask();
        $loMask = $bitmask->getMask($br->getCurrentBit(), BitMask::MASK_LO);
        $hiMask = $bitmask->getMask($br->getCurrentBit(), BitMask::MASK_HI);
        $hiBits = $br->getNextByte() & $hiMask;
        $loBits = $data & $loMask;
        $br->setNextByte($data);

        return $hiBits | $loBits;
    }

    /**
     * @param string $endian
     */
    public function setEndian($endian)
    {
        $this->endian = $endian;
    }

    /**
     * @return string
     */
    public function getEndian()
    {
        return $this->endian;
    }
}


<?php

namespace PhpBinaryReader\Type;

use PhpBinaryReader\BinaryReader;
use PhpBinaryReader\Exception\InvalidDataException;

class Byte implements TypeInterface
{
    /**
     * Returns an variable number of bytes
     *
     * @param  \PhpBinaryReader\BinaryReader $br
     * @param  int|null                      $length
     * @return string
     * @throws \OutOfBoundsException
     * @throws InvalidDataException
     */
    public function read(BinaryReader &$br, $length = null)
    {
        if (!is_int($length)) {
            throw new InvalidDataException('The length parameter must be an integer');
        }

        $br->align();

        if (!$br->canReadBytes($length)) {
            throw new \OutOfBoundsException('Cannot read bytes, it exceeds the boundary of the file');
        }

        $segment = $br->readFromHandle($length);

        return $segment;
    }
}


<?php

namespace PhpBinaryReader;

class Endian
{
    const ENDIAN_BIG = 1;
    const ENDIAN_LITTLE = 2;
    const BIG = 1;
    const LITTLE = 2;

    /**
     * Converts the endianess of a number from big to little or vise-versa
     *
     * @param  int $value
     * @return int
     */
    public function convert($value)
    {
        $data = dechex($value);

        if (strlen($data) <= 2) {
            return $value;
        }

        $unpack = unpack("H*", strrev(pack("H*", $data)));
        $converted = hexdec($unpack[1]);

        return $converted;
    }
}


<?php

namespace PhpBinaryReader\Type;

use PhpBinaryReader\BinaryReader;
use PhpBinaryReader\BitMask;
use PhpBinaryReader\Exception\InvalidDataException;

class Bit implements TypeInterface
{
    /**
     * @var bool
     */
    private $signed = false;

    /**
     * Returns an unsigned integer from the bit level
     *
     * @param  \PhpBinaryReader\BinaryReader $br
     * @param  int                           $length
     * @throws \OutOfBoundsException
     * @throws InvalidDataException
     * @return int
     */
    public function read(BinaryReader &$br, $length)
    {
        if (!is_int($length)) {
            throw new InvalidDataException('The length parameter must be an integer');
        }

        $bitmask = new BitMask();
        $result = 0;
        $bits = $length;
        $shift = $br->getCurrentBit();

        if ($shift != 0) {
            $bitsLeft = 8 - $shift;

            if ($bitsLeft < $bits) {
                $bits -= $bitsLeft;
                $result = ($br->getNextByte() >> $shift) << $bits;
            } elseif ($bitsLeft > $bits) {
                $br->setCurrentBit($br->getCurrentBit() + $bits);

                return ($br->getNextByte() >> $shift) & $bitmask->getMask($bits, BitMask::MASK_LO);
            } else {
                $br->setCurrentBit(0);

                return $br->getNextByte() >> $shift;
            }
        }

        if (!$br->canReadBytes($length / 8)) {
            throw new \OutOfBoundsException('Cannot read bits, it exceeds the boundary of the file');
        }

        if ($bits >= 8) {
            $bytes = intval($bits / 8);

            if ($bytes == 1) {
                $bits -= 8;
                $result |= ($this->getSigned() ? $br->readInt8() : $br->readUInt8()) << $bits;
            } elseif ($bytes == 2) {
                $bits -= 16;
                $result |= ($this->getSigned() ? $br->readInt16() : $br->readUInt16()) << $bits;
            } elseif ($bytes == 4) {
                $bits -= 32;
                $result |= ($this->getSigned() ? $br->readInt32() : $br->readUInt32()) << $bits;
            } else {
                while ($bits > 8) {
                    $bits -= 8;
                    $result |= ($this->getSigned() ? $br->readInt8() : $br->readUInt8()) << 8;
                }
            }
        }

        if ($bits != 0) {
            $code = $this->getSigned() ? 'c' : 'C';
            $data = unpack($code, $br->readFromHandle(1));
            $br->setNextByte($data[1]);
            $result |= $br->getNextByte() & $bitmask->getMask($bits, BitMask::MASK_LO);
        }

        $br->setCurrentBit($bits);

        return $result;
    }

    /**
     * Returns a signed integer from the bit level
     *
     * @param  \PhpBinaryReader\BinaryReader $br
     * @param  int                           $length
     * @return int
     */
    public function readSigned(&$br, $length)
    {
        $this->setSigned(true);
        $value = $this->read($br, $length);
        $this->setSigned(false);

        return $value;
    }

    /**
     * @param boolean $signed
     */
    public function setSigned($signed)
    {
        $this->signed = $signed;
    }

    /**
     * @return boolean
     */
    public function getSigned()
    {
        return $this->signed;
    }
}


<?php

namespace PhpBinaryReader\Type;

use PhpBinaryReader\BinaryReader;
use PhpBinaryReader\BitMask;
use PhpBinaryReader\Endian;

class Int16 implements TypeInterface
{
    /**
     * @var string
     */
    private $endianBig = 'n';

    /**
     * @var string
     */
    private $endianLittle = 'v';

    /**
     * Returns an Unsigned 16-bit Integer
     *
     * @param  \PhpBinaryReader\BinaryReader $br
     * @param  null                          $length
     * @return int
     * @throws \OutOfBoundsException
     */
    public function read(BinaryReader &$br, $length = null)
    {
        if (!$br->canReadBytes(2)) {
            throw new \OutOfBoundsException('Cannot read 16-bit int, it exceeds the boundary of the file');
        }

        $endian = $br->getEndian() == Endian::ENDIAN_BIG ? $this->endianBig : $this->endianLittle;
        $segment = $br->readFromHandle(2);

        $data = unpack($endian, $segment);
        $data = $data[1];

        if ($br->getCurrentBit() != 0) {
            $data = $this->bitReader($br, $data);
        }

        return $data;
    }

    /**
     * Returns a Signed 16-bit Integer
     *
     * @param  \PhpBinaryReader\BinaryReader $br
     * @return int
     */
    public function readSigned(&$br)
    {
        $this->setEndianBig('s');
        $this->setEndianLittle('s');

        $value = $this->read($br);

        $this->setEndianBig('n');
        $this->setEndianLittle('v');

        $endian = new Endian();
        if ($br->getMachineByteOrder() != Endian::ENDIAN_LITTLE && $br->getEndian() == Endian::ENDIAN_LITTLE) {
            return $endian->convert($value);
        } else {
            return $value;
        }
    }

    /**
     * @param  \PhpBinaryReader\BinaryReader $br
     * @param  int                           $data
     * @return int
     */
    private function bitReader(&$br, $data)
    {
        $bitmask = new BitMask();
        $loMask = $bitmask->getMask($br->getCurrentBit(), BitMask::MASK_LO);
        $hiMask = $bitmask->getMask($br->getCurrentBit(), BitMask::MASK_HI);
        $hiBits = ($br->getNextByte() & $hiMask) << 8;
        $miBits = ($data & 0xFF00) >> (8 - $br->getCurrentBit());
        $loBits = ($data & $loMask);
        $br->setNextByte($data & 0xFF);

        return $hiBits | $miBits | $loBits;
    }

    /**
     * @param string $endianBig
     */
    public function setEndianBig($endianBig)
    {
        $this->endianBig = $endianBig;
    }

    /**
     * @return string
     */
    public function getEndianBig()
    {
        return $this->endianBig;
    }

    /**
     * @param string $endianLittle
     */
    public function setEndianLittle($endianLittle)
    {
        $this->endianLittle = $endianLittle;
    }

    /**
     * @return string
     */
    public function getEndianLittle()
    {
        return $this->endianLittle;
    }
}


<?php

namespace PhpBinaryReader\Type;

use PhpBinaryReader\BinaryReader;
use PhpBinaryReader\BitMask;
use PhpBinaryReader\Endian;

class Int64 implements TypeInterface
{
    /**
     * @var string
     */
    private $endianBig = 'N';

    /**
     * @var string
     */
    private $endianLittle = 'V';

    /**
     * Returns an Unsigned 64-bit Integer
     *
     * @param  \PhpBinaryReader\BinaryReader $br
     * @param  null                          $length
     * @return int
     * @throws \OutOfBoundsException
     */
    public function read(BinaryReader &$br, $length = null)
    {
        if (!$br->canReadBytes(8)) {
            throw new \OutOfBoundsException('Cannot read 64-bit int, it exceeds the boundary of the file');
        }

        $endian = $br->getEndian() == Endian::ENDIAN_BIG ? $this->endianBig : $this->endianLittle;
        $firstSegment = $br->readFromHandle(4);
        $secondSegment = $br->readFromHandle(4);

        $firstHalf = unpack($endian, $firstSegment)[1];
        $secondHalf = unpack($endian, $secondSegment)[1];

        if ($br->getEndian() == Endian::ENDIAN_BIG) {
            $value = bcadd($secondHalf, bcmul($firstHalf, "4294967296"));
        } else {
            $value = bcadd($firstHalf, bcmul($secondHalf, "4294967296"));
        }

        if ($br->getCurrentBit() != 0) {
            $value = $this->bitReader($br, $value);
        }

        return $value;
    }

    /**
     * Returns a Signed 64-Bit Integer
     *
     * @param  \PhpBinaryReader\BinaryReader $br
     * @return int
     */
    public function readSigned(&$br)
    {
        $value = $this->read($br);
        if (bccomp($value, bcpow(2, 63)) >= 0) {
            $value = bcsub($value, bcpow(2, 64));
        }

        return $value;
    }

    /**
     * @param  \PhpBinaryReader\BinaryReader $br
     * @param  int                           $data
     * @return int
     */
    private function bitReader(&$br, $data)
    {
        $bitmask = new BitMask();
        $loMask = $bitmask->getMask($br->getCurrentBit(), BitMask::MASK_LO);
        $hiMask = $bitmask->getMask($br->getCurrentBit(), BitMask::MASK_HI);
        $hiBits = ($br->getNextByte() & $hiMask) << 56;
        $miBits = ($data & 0xFFFFFFFFFFFFFF00) >> (8 - $br->getCurrentBit());
        $loBits = ($data & $loMask);
        $br->setNextByte($data & 0xFF);

        return $hiBits | $miBits | $loBits;
    }

    /**
     * @param string $endianBig
     */
    public function setEndianBig($endianBig)
    {
        $this->endianBig = $endianBig;
    }

    /**
     * @return string
     */
    public function getEndianBig()
    {
        return $this->endianBig;
    }

    /**
     * @param string $endianLittle
     */
    public function setEndianLittle($endianLittle)
    {
        $this->endianLittle = $endianLittle;
    }

    /**
     * @return string
     */
    public function getEndianLittle()
    {
        return $this->endianLittle;
    }
}


<?php
/**
 * Copyright 2018 OpenCensus Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace OpenCensus\Trace\Exporter;

use Google\Cloud\Core\Batch\BatchRunner;
use Google\Cloud\Core\Batch\BatchTrait;
use Google\Cloud\Trace\TraceClient;
use Google\Cloud\Trace\Span;
use Google\Cloud\Trace\Trace;
use OpenCensus\Trace\SpanData;
use OpenCensus\Trace\Exporter\Stackdriver\SpanConverter;

/**
 * This implementation of the ExporterInterface use the BatchRunner to provide
 * reporting of Traces and their Spans to Google Cloud Stackdriver Trace.
 *
 * Example:
 * ```
 * use OpenCensus\Trace\Tracer;
 * use OpenCensus\Trace\Exporter\StackdriverExporter;
 *
 * $reporter = new StackdriverExporter([
 *   'clientConfig' => [
 *      'projectId' => 'my-project'
 *   ]
 * ]);
 * Tracer::start($reporter);
 * ```
 *
 * The above configuration will synchronously report the traces to Google Cloud
 * Stackdriver Trace. You can enable an experimental asynchronous reporting
 * mechanism using
 * BatchDaemon.
 * To enable asynchronous exporting, set the
 * `IS_BATCH_DAEMON_RUNNING` environment variable to `true`.
 */
class StackdriverExporter implements ExporterInterface
{
    const VERSION = '0.1.0';

    use BatchTrait;

    /**
     * @var TraceClient
     */
    private static $client;

    /**
     * @var array
     */
    private $clientConfig;

    /**
     * Create a TraceExporter that utilizes background batching.
     *
     * @param array $options [optional] Configuration options.
     *
     *     @type TraceClient $client A trace client used to instantiate traces
     *           to be delivered to the batch queue.
     *     @type bool $debugOutput Whether or not to output debug information.
     *           Please note debug output currently only applies in CLI based
     *           applications. **Defaults to** `false`.
     *     @type array $batchOptions A set of options for a BatchJob. See
     *           \Google\Cloud\Core\Batch\BatchJob::__construct()
     *           for more details.
     *           **Defaults to** ['batchSize' => 1000,
     *                            'callPeriod' => 2.0,
     *                            'workerNum' => 2].
     *     @type array $clientConfig Configuration options for the Trace client
     *           used to handle processing of batch items.
     *           For valid options please see
     *           \Google\Cloud\Trace\TraceClient::__construct().
     *     @type BatchRunner $batchRunner A BatchRunner object. Mainly used for
     *           the tests to inject a mock. **Defaults to** a newly created
     *           BatchRunner.
     *     @type string $identifier An identifier for the batch job.
     *           **Defaults to** `stackdriver-trace`.
     */
    public function __construct(array $options = [])
    {
        $options += [
            'async' => false,
            'client' => null
        ];
        $this->setCommonBatchProperties($options + [
            'identifier' => 'stackdriver-trace',
            'batchMethod' => 'insertBatch'
        ]);
        self::$client = $options['client'] ?: new TraceClient($this->clientConfig);
    }

    /**
     * Report the provided Trace to a backend.
     *
     * @param SpanData[] $spans
     * @return bool
     */
    public function export(array $spans)
    {
        if (empty($spans)) {
            return false;
        }

        // Pull the traceId from the first span
        $spans = array_map([SpanConverter::class, 'convertSpan'], $spans);
        $rootSpan = $spans[0];
        $trace = self::$client->trace(
            $rootSpan->traceId()
        );

        // build a Trace object and assign Spans
        $trace->setSpans($spans);

        try {
            return $this->batchRunner->submitItem($this->identifier, $trace);
        } catch (\Exception $e) {
            error_log('Reporting the Trace data failed: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Returns an array representation of a callback which will be used to write
     * batch items.
     *
     * @return array
     */
    protected function getCallback()
    {
        if (!isset(self::$client)) {
            self::$client = new TraceClient($this->clientConfig);
        }

        return [self::$client, $this->batchMethod];
    }
}


<?php
/**
 * Class for parsing robots.txt-files.
 *
 * @package phpcrawl
 * @internal
 */
class PHPCrawlerRobotsTxtParser
{
  /**
   * A PHPCrawlerHTTPRequest-object for requesting robots.txt-files.
   *
   * @var PHPCrawlerHTTPRequest
   */
  protected $PageRequest;

  public function __construct()
  {
    // Init PageRequest-class
    if (!class_exists("PHPCrawlerHTTPRequest")) include_once($classpath."/PHPCrawlerHTTPRequest.class.php");
    $this->PageRequest = new PHPCrawlerHTTPRequest();
  }

  /**
   * Parses the robots.txt-file related to the given URL and returns regular-expression-rules
   * corresponding to the containing "disallow"-rules that are adressed to the given user-agent.
   *
   * @param PHPCrawlerURLDescriptor $Url The URL
   * @param string $user_agent_string User-agent.
   *
   * @return array Numeric array containing regular-expressions for each "disallow"-rule defined in the robots.txt-file
   *               that's adressed to the given user-agent.
   */
  public function parseRobotsTxt(PHPCrawlerURLDescriptor $Url, $user_agent_string)
  {
    PHPCrawlerBenchmark::start("processing_robotstxt");

    // URL of robots-txt
    $RobotsTxtUrl = self::getRobotsTxtURL($Url);

    // Get robots.txt-content related to the given URL
    $robots_txt_content = $this->getRobotsTxtContent($RobotsTxtUrl);

    $non_follow_reg_exps = array();

    // If content was found
    if ($robots_txt_content != null)
    {
      // Get all lines in the robots.txt-content that are adressed to our user-agent.
      $applying_lines = $this->getApplyingLines($robots_txt_content, $user_agent_string);

      // Get valid reg-expressions for the given disallow-pathes.
      $non_follow_reg_exps = $this->buildRegExpressions($applying_lines, PHPCrawlerUtils::getRootUrl($Url->url_rebuild));
    }

    PHPCrawlerBenchmark::stop("processing_robots.txt");

    return $non_follow_reg_exps;
  }

  /**
   * Function returns all RAW lines in the given robots.txt-content that apply to
   * the given useragent-string.
   *
   * @return array Numeric array with found lines
   */
  protected function getApplyingLines(&$robots_txt_content, $user_agent_string)
  {
    // Split the content into its lines
    $robotstxt_lines = explode("\n", $robots_txt_content);

    // Flag that will get TRUE if the loop over the lines gets
    // into a section that applies to our user_agent_string
    $matching_section = false;

    // Flag that indicats if the loop is in a "agent-define-section"
    // (the parts/blocks that contain the "User-agent"-lines.)
    $agent_define_section = false;

    // Flag that indicates if we have found a section that fits to our
    // User-agent
    $matching_section_found = false;

    // Array to collect all the lines that applie to our user_agent
    $applying_lines = array();

    // Loop over the lines
    $cnt = count($robotstxt_lines);
    for ($x=0; $x<$cnt; $x++)
    {
      $robotstxt_lines[$x] = trim($robotstxt_lines[$x]);

      // Check if a line begins with "User-agent"
      if (preg_match("#^User-agent:# i", $robotstxt_lines[$x]))
      {
        // If a new "user-agent" section begins -> reset matching_section-flag
        if ($agent_define_section == false)
        {
          $matching_section = false;
        }

        $agent_define_section = true; // Now we are in an agent-define-section

        // The user-agent specified in the "User-agent"-line
        preg_match("#^User-agent:[ ]*(.*)$# i", $robotstxt_lines[$x], $match);
        $user_agent_section = trim($match[1]);

        // if the specified user-agent in the line fits to our user-agent-String (* fits always)
        // -> switch the flag "matching_section" to true
        if ($user_agent_section == "*" || preg_match("#^".preg_quote($user_agent_section)."# i", $user_agent_string))
        {
          $matching_section = true;
          $matching_section_found = true;
        }

        continue; // Don't do anything else with the "User-agent"-lines, just go on
      }
      else
      {
        // We are not in an agent-define-section (anymore)
        $agent_define_section = false;
      }

      // If we are in a section that applies to our user_agent
      // -> store the line.
      if ($matching_section == true)
      {
        $applying_lines[] = $robotstxt_lines[$x];
      }

      // If we are NOT in a matching section (anymore) AND we've already found
      // and parsed a matching section -> stop looking further (thats what RFC says)
      if ($matching_section == false && $matching_section_found == true)
      {
        // break;
      }
    }

    return $applying_lines;
  }

  /**
   * Returns an array containig regular-expressions corresponding
   * to the given robots.txt-style "Disallow"-lines
   *
   * @param array &$applying_lines Numeric array containing "disallow"-lines.
   * @param string $base_url       Base-URL the robots.txt-file was found in.
   *
   * @return array  Numeric array containing regular-expresseions created for each "disallow"-line.
   */
  protected function buildRegExpressions(&$applying_lines, $base_url)
  {
    // First, get all "Disallow:"-pathes
    $disallow_pathes = array();
    for ($x=0; $x<count($applying_lines); $x++)
    {
      if (preg_match("#^Disallow:# i", $applying_lines[$x]))
      {
        preg_match("#^Disallow:[ ]*(.*)#", $applying_lines[$x], $match);
        $disallow_pathes[] = trim($match[1]);
      }
    }

    // Works like this:
    // The base-url is http://www.foo.com.
    // The driective says: "Disallow: /bla/"
    // This means: The nonFollowMatch is "#^http://www\.foo\.com/bla/#"

    $normalized_base_url = PHPCrawlerUtils::normalizeURL($base_url);

    $non_follow_expressions = array();

    for ($x=0; $x<count($disallow_pathes); $x++)
    {
      // If the disallow-path is empty -> simply ignore it
      if ($disallow_pathes[$x] == "") continue;

      $non_follow_path_complpete = $normalized_base_url.substr($disallow_pathes[$x], 1); // "http://www.foo.com/bla/"
      $non_follow_exp = preg_quote($non_follow_path_complpete, "#"); // "http://www\.foo\.com/bla/"
      $non_follow_exp = "#^".$non_follow_exp."#"; // "#^http://www\.foo\.com/bla/#"

      $non_follow_expressions[] = $non_follow_exp;
    }

    return $non_follow_expressions;
  }

  /**
   * Retreives the content of a robots.txt-file
   *
   * @param PHPCrawlerURLDescriptor $Url The URL of the robots.txt-file
   * @return string The content of the robots.txt or NULL if no robots.txt was found.
   */
  protected function getRobotsTxtContent(PHPCrawlerURLDescriptor $Url)
  {
    // Request robots-txt
    $this->PageRequest->setUrl($Url);
    $PageInfo = $this->PageRequest->sendRequest();

    // Return content of the robots.txt-file if it was found, otherwie
    // reutrn NULL
    if ($PageInfo->http_status_code == 200)
    {
      return $PageInfo->content;
    }
    else
    {
      return null;
    }
  }

  /**
   * Returns the Robots.txt-URL related to the given URL
   *
   * @param PHPCrawlerURLDescriptor $Url  The URL as PHPCrawlerURLDescriptor-object
   * @return PHPCrawlerURLDescriptor Url of the related to the passed URL.
   */
  public static function getRobotsTxtURL(PHPCrawlerURLDescriptor $Url)
  {
    $url_parts = PHPCrawlerUtils::splitURL($Url->url_rebuild);
    $robots_txt_url = $url_parts["protocol"].$url_parts["host"].":".$url_parts["port"] . "/robots.txt";

    return new PHPCrawlerURLDescriptor($robots_txt_url);
  }
}

?>