<commit_before><?php
class RegistryPage extends Page {

	public static $db = array(
		'DataClass' => 'Varchar(100)',
		'PageLength' => 'Int'
	);

	public static $page_length_default = 10;

	public function getDataClasses() {
		$map = array();
		foreach(ClassInfo::implementorsOf('RegistryDataInterface') as $class) {
			$map[$class] = singleton($class)->singular_name();
		}
		return $map;
	}

	public function getDataClass() {
		return $this->getField('DataClass');
	}

	public function getDataSingleton() {
		return singleton($this->getDataClass());
	}

	public function getPageLength() {
		$length = $this->getField('PageLength');
		return $length ? $length : self::$page_length_default;
	}

	public function getCMSFields() {
		$fields = parent::getCMSFields();
		$classDropdown = new DropdownField('DataClass', 'Data class', $this->getDataClasses());
		$classDropdown->setEmptyString('Select one');
		$fields->addFieldToTab('Root.Main', $classDropdown, 'Content');
		$fields->addFieldToTab('Root.Main', new NumericField('PageLength', 'Results page length'), 'Content');
		return $fields;
	}

	public function LastUpdated() {
		$elements = new DataList($this->dataClass);
		$lastUpdated = new SS_Datetime('LastUpdated');
		$lastUpdated->setValue($elements->max('LastEdited'));
		return $lastUpdated;
	}

}
class RegistryPage_Controller extends Page_Controller {

	public static $allowed_actions = array(
		'Form',
		'show',
		'export'
	);

	/**
	 * Get all search query vars, compiled into a query string for a URL.
	 * This will escape all the variables to avoid XSS.
	 *
	 * @return string
	 */
	public function AllQueryVars() {
		return Convert::raw2xml(http_build_query($this->_queryVars()));
	}

	/**
	 * Get all search query vars except Sort and Dir, compiled into a query link.
	 * This will escape all the variables to avoid XSS.
	 *
	 * @return string
	 */
	public function QueryLink() {
		$vars = $this->_queryVars();
		unset($vars['Sort']);
		unset($vars['Dir']);

		return Convert::raw2xml($this->Link('Form') . '?' . http_build_query($vars));
	}

	public function Sort() {
		return isset($_GET['Sort']) ? $_GET['Sort'] : '';
	}

	/**
	 * If a direction is already set, return the opposite direction.
	 * Otherwise, just return a default sorting direction.
	 * @return string
	 */
	public function Direction() {
		return (isset($_GET['Dir']) && $_GET['Dir'] == 'ASC') ? 'DESC' : 'ASC';
	}

	public function Form() {
		$fields = $this->dataRecord->getDataSingleton()->getSearchFields();
		$fields->merge(new FieldList(
			new HiddenField('Sort', ''),
			new HiddenField('Dir', '')
		));

		$form = new Form($this, 'Form', $fields, new FieldList(
			new FormAction('doRegistrySearch', 'Search')
		));

		// load existing request GET data into the form if available
		$vars = $this->getRequest()->getVars();
		if($vars) {
			// default sorting when no sort/direction set
			if(empty($vars['Sort'])) $vars['Sort'] = 'ID';
			if(empty($vars['Dir'])) $vars['Dir'] = 'ASC';

			$form->loadDataFrom($vars);
		}

		$form->setFormMethod('GET');
		$form->disableSecurityToken();

		return $form;
	}

	public function RegistryEntries() {
		$pageLength = $this->dataRecord->getPageLength();
		$start = isset($_GET['start']) ? (int) $_GET['start'] : 0;
		$sort = isset($_GET['Sort']) ? $_GET['Sort'] : 'ID';
		
		$orderby = array();
		$direction = (!empty($_GET['Dir']) && in_array($_GET['Dir'], array('ASC', 'DESC'))) ? $_GET['Dir'] : 'ASC';
		$orderby = array();
		if ($sort) $orderby[$sort] = $direction;

		return $this->queryList(array(), $orderby, $start, $pageLength, true);
	}

	public function Columns($result = null) {
		$columns = $this->dataRecord->getDataSingleton()->summaryFields();
		$list = new ArrayList();
		foreach($columns as $name => $title) {
			$list->push(new ArrayData(array(
				'Name' => $name,
				'Title' => $title,
				'Value' => ($result ? $result->obj($name) : '')
			)));
		}
		return $list;
	}

	/**
	 * Build up search filters from user's search criteria and hand off
	 * to the {@link query()} method to search against the database.
	 *
	 * @param array $data Form request data
	 * @param Form Form object for submitted form
	 * @param SS_HTTPRequest
	 * @return array
	 */
	public function doRegistrySearch($data, $form, $request) {
		$pageLength = $this->dataRecord->getPageLength();
		$start = isset($_GET['start']) ? (int) $_GET['start'] : 0;
		$paged = isset($data['Unpaged']) ? false : true;
		$sort = isset($data['Sort']) ? $data['Sort'] : 'ID';
		$direction = (!empty($data['Dir']) && in_array($data['Dir'], array('ASC', 'DESC'))) ? $data['Dir'] : 'ASC';
		$orderby = array();
		$where = array();
		$results = new ArrayList();

		foreach($this->dataRecord->getDataSingleton()->getSearchFields() as $field) {
			if(!empty($data[$field->getName()])) {
				$where[] = sprintf('"%s" LIKE \'%%%s%%\'', $field->getName(), Convert::raw2sql($data[$field->getName()]));
			}
		}

		if ($sort) $orderby[$sort] = $direction;

		$results = $this->queryList($where, $orderby, $start, $pageLength, $paged);

		unset($data['url']);

		return array(
			'BackLink' => Convert::raw2xml(urlencode(http_build_query($data))),
			'Results' => $results
		);
	}

	/**
	 * Exports out all the data for the current search results.
	 * Sends the data to the browser as a CSV file.
	 */
	public function export($request) {
		$dataClass = $this->dataRecord->getDataClass();
		$resultColumns = $this->dataRecord->getDataSingleton()->fieldLabels();
		$vars = $request->getVars();
		$vars['Unpaged'] = true;

		$search = $this->doRegistrySearch(
			$vars,
			new Form($this, 'Form', new FieldList(), new FieldList()),
			new SS_HTTPRequest('GET', '')
		);

		if(!isset($search['Results'])) return array();

		if (!file_exists(REGISTRY_EXPORT_PATH)) {
			mkdir(REGISTRY_EXPORT_PATH);
		}
		$base = REGISTRY_EXPORT_PATH . '/' . $dataClass;
		if(!file_exists($base)) {
			mkdir($base);
		}

		$filepath = sprintf('%s/export-%s.csv', $base, date('Y-m-dHis'));
		$file = fopen($filepath, 'w');

		$cols = array_keys($resultColumns);
		// put the headers in the first row
		fputcsv($file, $cols, ',', '"');

		// put the data in the rows after
		foreach($search['Results'] as $result) {
			$item = array();
			foreach($cols as $col) {
				$item[] = $result->$col;
			}
			fputcsv($file, $item, ',', '"');
		}

		fclose($file);

		// if the headers can't be sent (i.e. running a unit test, or something)
		// just return the file path so the user can manually download the csv
		if(!headers_sent() && !SapphireTest::is_running_test()) {
			header('Content-Description: File Transfer');
			header('Content-Type: application/octet-stream');
			header('Content-Disposition: attachment; filename=' . basename($filepath));
			header('Content-Transfer-Encoding: binary');
			header('Expires: 0');
			header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
			header('Pragma: public');
			header('Content-Length: ' . filesize($filepath));
			ob_clean();
			flush();
			readfile($filepath);

			unlink($filePath);
		} else {
			$contents = file_get_contents($filepath);
			unlink($filePath);
			return $contents;
		}
	}

	public function show($request) {
		$data = DataObject::get_by_id($this->DataClass, $request->param('ID'));
		if(!($data && $data->exists())) {
			return $this->httpError(404);
		}

		return $this->customise($data)->renderWith($this->getTemplateList('show'));
	}

	/**
	 * Perform a search against the data table.
	 *
	 * @param array $where Array of strings to add into the WHERE clause
	 * @param array $orderby Array of column as key, to direction as value to add into the ORDER BY clause
	 * @param string|int $start Record to start at (for paging)
	 * @param string|int $pageLength Number of results per page (for paging)
	 * @param boolean $paged Paged results or not?
	 * @return ArrayList|PaginatedList
	 */
	protected function queryList($where = array(), $orderby = array(), $start, $pageLength, $paged = true) {
		$dataClass = $this->dataRecord->getDataClass();
		$resultColumns = $this->dataRecord->getDataSingleton()->summaryFields();
		$resultColumns['ID'] = 'ID';
		$results = new ArrayList();

		$query = new SQLQuery();
		$query->setSelect(array_keys($resultColumns))->setFrom($dataClass);
		$query->addWhere($where);
		$query->addOrderBy($orderby);
		$query->setConnective('AND');

		if($paged) {
			$query->setLimit($pageLength, $start);
		}

		foreach($query->execute() as $record) {
			$result = new $dataClass($record);
			$result->Columns = $this->Columns($result); // we attach Columns here so the template can loop through them on each result
			$results->push($result);
		}

		if($paged) {
			$list = new PaginatedList($results);
			$list->setPageStart($start);
			$list->setPageLength($pageLength);
			$list->setTotalItems($query->unlimitedRowCount());
			$list->setLimitItems(false);
		} else {
			$list = $results;
		}

		return $list;
	}

	/**
	 * Compiles all available GET variables for the result
	 * columns into an array. Used internally, not to be
	 * used directly with the templates or outside classes.
	 *
	 * This will NOT escape values to avoid XSS.
	 *
	 * @return array
	 */
	protected function _queryVars() {
		$resultColumns = $this->dataRecord->getDataSingleton()->getSearchFields();
		$columns = array();
		foreach($resultColumns as $field) {
			$columns[$field->getName()] = '';
		}

		$arr = array_merge(
			$columns,
			array(
				'action_doRegistrySearch' => 'Search',
				'Sort' => '',
				'Dir' => ''
			)
		);

		foreach($arr as $key => $val) {
			if(isset($_GET[$key])) $arr[$key] = $_GET[$key];
		}

		return $arr;
	}

	public function getTemplateList($action) {
		$templates = parent::getTemplateList($action);

		$actionlessTemplates = array();

		if($action && $action != 'index') {
			array_unshift($templates, 'RegistryPage_' . $this->DataClass . '_' . $action);
		}
		array_unshift($actionlessTemplates, 'RegistryPage_' . $this->DataClass);

		$parentClass = get_class($this->dataRecord);
		while($parentClass != 'RegistryPage') {
			if($action && $action != 'index') {
				array_unshift($templates, $parentClass . '_' . $this->DataClass . '_' . $action);
			}
			array_unshift($actionlessTemplates, $parentClass . '_' . $this->DataClass);

			$parentClass = get_parent_class($parentClass);
		}

		$index = 0;
		while ($index < count($templates) && $templates[$index] != 'RegistryPage') {
			$index++;
		}

		return array_merge(array_slice($templates, 0, $index), $actionlessTemplates, array_slice($templates, $index));
	}

}
<commit_msg>Pass through search results properly<commit_after><?php
class RegistryPage extends Page {

	public static $db = array(
		'DataClass' => 'Varchar(100)',
		'PageLength' => 'Int'
	);

	public static $page_length_default = 10;

	public function getDataClasses() {
		$map = array();
		foreach(ClassInfo::implementorsOf('RegistryDataInterface') as $class) {
			$map[$class] = singleton($class)->singular_name();
		}
		return $map;
	}

	public function getDataClass() {
		return $this->getField('DataClass');
	}

	public function getDataSingleton() {
		return singleton($this->getDataClass());
	}

	public function getPageLength() {
		$length = $this->getField('PageLength');
		return $length ? $length : self::$page_length_default;
	}

	public function getCMSFields() {
		$fields = parent::getCMSFields();
		$classDropdown = new DropdownField('DataClass', 'Data class', $this->getDataClasses());
		$classDropdown->setEmptyString('Select one');
		$fields->addFieldToTab('Root.Main', $classDropdown, 'Content');
		$fields->addFieldToTab('Root.Main', new NumericField('PageLength', 'Results page length'), 'Content');
		return $fields;
	}

	public function LastUpdated() {
		$elements = new DataList($this->dataClass);
		$lastUpdated = new SS_Datetime('LastUpdated');
		$lastUpdated->setValue($elements->max('LastEdited'));
		return $lastUpdated;
	}

}
class RegistryPage_Controller extends Page_Controller {

	public static $allowed_actions = array(
		'Form',
		'show',
		'export'
	);

	/**
	 * Get all search query vars, compiled into a query string for a URL.
	 * This will escape all the variables to avoid XSS.
	 *
	 * @return string
	 */
	public function AllQueryVars() {
		return Convert::raw2xml(http_build_query($this->_queryVars()));
	}

	/**
	 * Get all search query vars except Sort and Dir, compiled into a query link.
	 * This will escape all the variables to avoid XSS.
	 *
	 * @return string
	 */
	public function QueryLink() {
		$vars = $this->_queryVars();
		unset($vars['Sort']);
		unset($vars['Dir']);

		return Convert::raw2xml($this->Link('Form') . '?' . http_build_query($vars));
	}

	public function Sort() {
		return isset($_GET['Sort']) ? $_GET['Sort'] : '';
	}

	/**
	 * If a direction is already set, return the opposite direction.
	 * Otherwise, just return a default sorting direction.
	 * @return string
	 */
	public function Direction() {
		return (isset($_GET['Dir']) && $_GET['Dir'] == 'ASC') ? 'DESC' : 'ASC';
	}

	public function Form() {
		$fields = $this->dataRecord->getDataSingleton()->getSearchFields();
		$fields->merge(new FieldList(
			new HiddenField('Sort', ''),
			new HiddenField('Dir', '')
		));

		$form = new Form($this, 'Form', $fields, new FieldList(
			new FormAction('doRegistrySearch', 'Search')
		));

		// load existing request GET data into the form if available
		$vars = $this->getRequest()->getVars();
		if($vars) {
			// default sorting when no sort/direction set
			if(empty($vars['Sort'])) $vars['Sort'] = 'ID';
			if(empty($vars['Dir'])) $vars['Dir'] = 'ASC';

			$form->loadDataFrom($vars);
		}

		$form->setFormMethod('GET');
		$form->disableSecurityToken();

		return $form;
	}

	public function RegistryEntries() {
		$pageLength = $this->dataRecord->getPageLength();
		$start = isset($_GET['start']) ? (int) $_GET['start'] : 0;
		$sort = isset($_GET['Sort']) ? $_GET['Sort'] : 'ID';
		
		$orderby = array();
		$direction = (!empty($_GET['Dir']) && in_array($_GET['Dir'], array('ASC', 'DESC'))) ? $_GET['Dir'] : 'ASC';
		$orderby = array();
		if ($sort) $orderby[$sort] = $direction;

		return $this->queryList(array(), $orderby, $start, $pageLength, true);
	}

	public function Columns($result = null) {
		$columns = $this->dataRecord->getDataSingleton()->summaryFields();
		$list = new ArrayList();
		foreach($columns as $name => $title) {
			$list->push(new ArrayData(array(
				'Name' => $name,
				'Title' => $title,
				'Value' => ($result ? $result->obj($name) : '')
			)));
		}
		return $list;
	}

	/**
	 * Build up search filters from user's search criteria and hand off
	 * to the {@link query()} method to search against the database.
	 *
	 * @param array $data Form request data
	 * @param Form Form object for submitted form
	 * @param SS_HTTPRequest
	 * @return array
	 */
	public function doRegistrySearch($data, $form, $request) {
		$pageLength = $this->dataRecord->getPageLength();
		$start = isset($_GET['start']) ? (int) $_GET['start'] : 0;
		$paged = isset($data['Unpaged']) ? false : true;
		$sort = isset($data['Sort']) ? $data['Sort'] : 'ID';
		$direction = (!empty($data['Dir']) && in_array($data['Dir'], array('ASC', 'DESC'))) ? $data['Dir'] : 'ASC';
		$orderby = array();
		$where = array();
		$results = new ArrayList();

		foreach($this->dataRecord->getDataSingleton()->getSearchFields() as $field) {
			if(!empty($data[$field->getName()])) {
				$where[] = sprintf('"%s" LIKE \'%%%s%%\'', $field->getName(), Convert::raw2sql($data[$field->getName()]));
			}
		}

		if ($sort) $orderby[$sort] = $direction;

		$results = $this->queryList($where, $orderby, $start, $pageLength, $paged);

		unset($data['url']);

		return array(
			'BackLink' => Convert::raw2xml(urlencode(http_build_query($data))),
			'RegistryEntries' => $results
		);
	}

	/**
	 * Exports out all the data for the current search results.
	 * Sends the data to the browser as a CSV file.
	 */
	public function export($request) {
		$dataClass = $this->dataRecord->getDataClass();
		$resultColumns = $this->dataRecord->getDataSingleton()->fieldLabels();
		$vars = $request->getVars();
		$vars['Unpaged'] = true;

		$search = $this->doRegistrySearch(
			$vars,
			new Form($this, 'Form', new FieldList(), new FieldList()),
			new SS_HTTPRequest('GET', '')
		);

		if(!isset($search['Results'])) return array();

		if (!file_exists(REGISTRY_EXPORT_PATH)) {
			mkdir(REGISTRY_EXPORT_PATH);
		}
		$base = REGISTRY_EXPORT_PATH . '/' . $dataClass;
		if(!file_exists($base)) {
			mkdir($base);
		}

		$filepath = sprintf('%s/export-%s.csv', $base, date('Y-m-dHis'));
		$file = fopen($filepath, 'w');

		$cols = array_keys($resultColumns);
		// put the headers in the first row
		fputcsv($file, $cols, ',', '"');

		// put the data in the rows after
		foreach($search['Results'] as $result) {
			$item = array();
			foreach($cols as $col) {
				$item[] = $result->$col;
			}
			fputcsv($file, $item, ',', '"');
		}

		fclose($file);

		// if the headers can't be sent (i.e. running a unit test, or something)
		// just return the file path so the user can manually download the csv
		if(!headers_sent() && !SapphireTest::is_running_test()) {
			header('Content-Description: File Transfer');
			header('Content-Type: application/octet-stream');
			header('Content-Disposition: attachment; filename=' . basename($filepath));
			header('Content-Transfer-Encoding: binary');
			header('Expires: 0');
			header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
			header('Pragma: public');
			header('Content-Length: ' . filesize($filepath));
			ob_clean();
			flush();
			readfile($filepath);

			unlink($filePath);
		} else {
			$contents = file_get_contents($filepath);
			unlink($filePath);
			return $contents;
		}
	}

	public function show($request) {
		$data = DataObject::get_by_id($this->DataClass, $request->param('ID'));
		if(!($data && $data->exists())) {
			return $this->httpError(404);
		}

		return $this->customise($data)->renderWith($this->getTemplateList('show'));
	}

	/**
	 * Perform a search against the data table.
	 *
	 * @param array $where Array of strings to add into the WHERE clause
	 * @param array $orderby Array of column as key, to direction as value to add into the ORDER BY clause
	 * @param string|int $start Record to start at (for paging)
	 * @param string|int $pageLength Number of results per page (for paging)
	 * @param boolean $paged Paged results or not?
	 * @return ArrayList|PaginatedList
	 */
	protected function queryList($where = array(), $orderby = array(), $start, $pageLength, $paged = true) {
		$dataClass = $this->dataRecord->getDataClass();
		$resultColumns = $this->dataRecord->getDataSingleton()->summaryFields();
		$resultColumns['ID'] = 'ID';
		$results = new ArrayList();

		$query = new SQLQuery();
		$query->setSelect(array_keys($resultColumns))->setFrom($dataClass);
		$query->addWhere($where);
		$query->addOrderBy($orderby);
		$query->setConnective('AND');

		if($paged) {
			$query->setLimit($pageLength, $start);
		}

		foreach($query->execute() as $record) {
			$result = new $dataClass($record);
			$result->Columns = $this->Columns($result); // we attach Columns here so the template can loop through them on each result
			$results->push($result);
		}

		if($paged) {
			$list = new PaginatedList($results);
			$list->setPageStart($start);
			$list->setPageLength($pageLength);
			$list->setTotalItems($query->unlimitedRowCount());
			$list->setLimitItems(false);
		} else {
			$list = $results;
		}

		return $list;
	}

	/**
	 * Compiles all available GET variables for the result
	 * columns into an array. Used internally, not to be
	 * used directly with the templates or outside classes.
	 *
	 * This will NOT escape values to avoid XSS.
	 *
	 * @return array
	 */
	protected function _queryVars() {
		$resultColumns = $this->dataRecord->getDataSingleton()->getSearchFields();
		$columns = array();
		foreach($resultColumns as $field) {
			$columns[$field->getName()] = '';
		}

		$arr = array_merge(
			$columns,
			array(
				'action_doRegistrySearch' => 'Search',
				'Sort' => '',
				'Dir' => ''
			)
		);

		foreach($arr as $key => $val) {
			if(isset($_GET[$key])) $arr[$key] = $_GET[$key];
		}

		return $arr;
	}

	public function getTemplateList($action) {
		$templates = parent::getTemplateList($action);

		$actionlessTemplates = array();

		if($action && $action != 'index') {
			array_unshift($templates, 'RegistryPage_' . $this->DataClass . '_' . $action);
		}
		array_unshift($actionlessTemplates, 'RegistryPage_' . $this->DataClass);

		$parentClass = get_class($this->dataRecord);
		while($parentClass != 'RegistryPage') {
			if($action && $action != 'index') {
				array_unshift($templates, $parentClass . '_' . $this->DataClass . '_' . $action);
			}
			array_unshift($actionlessTemplates, $parentClass . '_' . $this->DataClass);

			$parentClass = get_parent_class($parentClass);
		}

		$index = 0;
		while ($index < count($templates) && $templates[$index] != 'RegistryPage') {
			$index++;
		}

		return array_merge(array_slice($templates, 0, $index), $actionlessTemplates, array_slice($templates, $index));
	}

}

{'fraction_non_alphanumeric': 0.18374022280161176, 'fraction_numerical': 0.0018013747333491348, 'mean_word_length': 4.350240933299518, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 195, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6234634', 'n_tokens_mistral': 7512, 'n_tokens_neox': 6719, 'n_words': 1620}