mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Fixed svn:eol-style settings
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@40456 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
7eb1c266a6
commit
44b68061d4
@ -1,34 +1,34 @@
|
||||
<?php
|
||||
class SSDatetime extends Date {
|
||||
function setValue($value) {
|
||||
if($value) $this->value = date('Y-m-d H:i:s', strtotime($value));
|
||||
else $value = null;
|
||||
}
|
||||
|
||||
function Nice() {
|
||||
return date('d/m/Y g:ia', strtotime($this->value));
|
||||
}
|
||||
function Nice24() {
|
||||
return date('d/m/Y H:i', strtotime($this->value));
|
||||
}
|
||||
function Date() {
|
||||
return date('d/m/Y', strtotime($this->value));
|
||||
}
|
||||
function Time() {
|
||||
return date('g:ia', strtotime($this->value));
|
||||
}
|
||||
function Time24() {
|
||||
return date('H:i', strtotime($this->value));
|
||||
}
|
||||
|
||||
function requireField() {
|
||||
DB::requireField($this->tableName, $this->name, "datetime");
|
||||
}
|
||||
|
||||
function __construct( $name ) {
|
||||
// Debug::show( 'Created SSDatetime: ' . $name );
|
||||
parent::__construct( $name );
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<?php
|
||||
class SSDatetime extends Date {
|
||||
function setValue($value) {
|
||||
if($value) $this->value = date('Y-m-d H:i:s', strtotime($value));
|
||||
else $value = null;
|
||||
}
|
||||
|
||||
function Nice() {
|
||||
return date('d/m/Y g:ia', strtotime($this->value));
|
||||
}
|
||||
function Nice24() {
|
||||
return date('d/m/Y H:i', strtotime($this->value));
|
||||
}
|
||||
function Date() {
|
||||
return date('d/m/Y', strtotime($this->value));
|
||||
}
|
||||
function Time() {
|
||||
return date('g:ia', strtotime($this->value));
|
||||
}
|
||||
function Time24() {
|
||||
return date('H:i', strtotime($this->value));
|
||||
}
|
||||
|
||||
function requireField() {
|
||||
DB::requireField($this->tableName, $this->name, "datetime");
|
||||
}
|
||||
|
||||
function __construct( $name ) {
|
||||
// Debug::show( 'Created SSDatetime: ' . $name );
|
||||
parent::__construct( $name );
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
|
@ -1,39 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Writes the POST array to a file as a last-ditch effort to preserve entered data.
|
||||
*/
|
||||
class PostBackup extends Object {
|
||||
|
||||
static function writeToFile($data, $controller, $form) {
|
||||
|
||||
// the static variable defines whether or not to backup a posted form
|
||||
if(!$form->stat('backup_post_data'))
|
||||
return;
|
||||
|
||||
// Append to the file
|
||||
if(!file_exists(BACKUP_DIR))
|
||||
mkdir(BACKUP_DIR, 0775, true);
|
||||
|
||||
$backupFile = fopen(BACKUP_DIR . '/' . $form->class, 'a');
|
||||
|
||||
$date = date('Y-m-d G:i:s');
|
||||
|
||||
$postData = var_export($data, true);
|
||||
|
||||
$backup = <<<BAK
|
||||
***BEGIN ENTRY***
|
||||
Date and time: {$date}
|
||||
URL: http://{$_SERVER['HTTP_HOST']}:{$_SERVER['SERVER_PORT']}{$_SERVER['PHP_SELF']}?{$_SERVER['QUERY_STRING']}
|
||||
Client IP: {$_SERVER['REMOTE_ADDR']}
|
||||
Controller: {$controller->class}
|
||||
|
||||
$postData
|
||||
***END ENTRY***
|
||||
BAK;
|
||||
|
||||
fwrite($backupFile, $backup);
|
||||
fclose($backupFile);
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
<?php
|
||||
/**
|
||||
* Writes the POST array to a file as a last-ditch effort to preserve entered data.
|
||||
*/
|
||||
class PostBackup extends Object {
|
||||
|
||||
static function writeToFile($data, $controller, $form) {
|
||||
|
||||
// the static variable defines whether or not to backup a posted form
|
||||
if(!$form->stat('backup_post_data'))
|
||||
return;
|
||||
|
||||
// Append to the file
|
||||
if(!file_exists(BACKUP_DIR))
|
||||
mkdir(BACKUP_DIR, 0775, true);
|
||||
|
||||
$backupFile = fopen(BACKUP_DIR . '/' . $form->class, 'a');
|
||||
|
||||
$date = date('Y-m-d G:i:s');
|
||||
|
||||
$postData = var_export($data, true);
|
||||
|
||||
$backup = <<<BAK
|
||||
***BEGIN ENTRY***
|
||||
Date and time: {$date}
|
||||
URL: http://{$_SERVER['HTTP_HOST']}:{$_SERVER['SERVER_PORT']}{$_SERVER['PHP_SELF']}?{$_SERVER['QUERY_STRING']}
|
||||
Client IP: {$_SERVER['REMOTE_ADDR']}
|
||||
Controller: {$controller->class}
|
||||
|
||||
$postData
|
||||
***END ENTRY***
|
||||
BAK;
|
||||
|
||||
fwrite($backupFile, $backup);
|
||||
fclose($backupFile);
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
|
1394
forms/TableField.php
1394
forms/TableField.php
@ -1,698 +1,698 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* TableField behaves in the same manner as TableListField, however allows the addition of
|
||||
* fields and editing of attributes specified, and filtering results.
|
||||
*
|
||||
* Caution: If you insert DropdownFields in the fieldTypes-array, make sure they have an empty first option.
|
||||
* Otherwise the saving can't determine if a new row should really be saved.
|
||||
*
|
||||
* Caution: TableField relies on {@FormResponse} to reload the field after it is saved.
|
||||
* A TableField-instance should never be saved twice without reloading, because otherwise it
|
||||
* can't determine if a field is new (=create) or existing (=update), and will produce duplicates.
|
||||
*
|
||||
* @param $name string The fieldname
|
||||
* @param $sourceClass string The source class of this field
|
||||
* @param $fieldList array An array of field headings of Fieldname => Heading Text (eg. heading1)
|
||||
* @param $fieldTypes array An array of field types of fieldname => fieldType (eg. formfield). Do not use for extra data/hiddenfields.
|
||||
* @param $filterField string DEPRECATED The actual limiting filter, eg. 1 (please use $sourceFilter in the form "ParentID = 1" instead)
|
||||
* @param $sourceFilter string The filter you wish to limit the objects by
|
||||
* @param $editExisting boolean (Note: Has to stay on this position for legacy reasons)
|
||||
* @param $sourceSort string
|
||||
* @param $sourceJoin string
|
||||
*
|
||||
* TODO We should refactor this to support a single FieldSet instead of evaluated Strings for building FormFields
|
||||
*/
|
||||
|
||||
class TableField extends TableListField {
|
||||
|
||||
protected $sourceClass;
|
||||
|
||||
protected $sourceFilter;
|
||||
|
||||
protected $fieldList;
|
||||
|
||||
/**
|
||||
* @var $fieldTypes FieldSet
|
||||
* Caution: Use {@setExtraData()} instead of manually adding HiddenFields if you want to
|
||||
* preset relations or other default data.
|
||||
*/
|
||||
protected $fieldTypes;
|
||||
|
||||
protected $sourceSort;
|
||||
|
||||
protected $sourceJoin;
|
||||
|
||||
/**
|
||||
* @var $template string Template-Overrides
|
||||
*/
|
||||
protected $template = "TableField";
|
||||
|
||||
/**
|
||||
* @var $extraData array Any extra data that need to be included, e.g. to retain
|
||||
* has-many relations. Format: array('FieldName' => 'Value')
|
||||
*/
|
||||
protected $extraData;
|
||||
|
||||
protected $tempForm;
|
||||
|
||||
/**
|
||||
* Influence output without having to subclass the template.
|
||||
*/
|
||||
protected $permissions = array(
|
||||
"edit",
|
||||
"delete",
|
||||
"add",
|
||||
//"export",
|
||||
);
|
||||
|
||||
public $transformationConditions = array();
|
||||
|
||||
/**
|
||||
* @var $requiredFields array Required fields as a numerical array.
|
||||
* Please use an instance of Validator on the including
|
||||
* form.
|
||||
*/
|
||||
protected $requiredFields = null;
|
||||
|
||||
/**
|
||||
* For some table, we want Can(add) to be true, so we can add,
|
||||
* but we don't want the default add row to be presented in the
|
||||
* table, we can set this wantDefaultAddRow to be false.
|
||||
* @param boolean $wantDefaultAddRow
|
||||
*/
|
||||
protected $wantDefaultAddRow = true;
|
||||
|
||||
function __construct($name, $sourceClass, $fieldList, $fieldTypes, $filterField = null,
|
||||
$sourceFilter = null, $editExisting = true, $sourceSort = null, $sourceJoin = null) {
|
||||
|
||||
$this->fieldTypes = $fieldTypes;
|
||||
$this->filterField = $filterField;
|
||||
|
||||
$this->editExisting = $editExisting;
|
||||
parent::__construct($name, $sourceClass, $fieldList, $sourceFilter, $sourceSort, $sourceJoin);
|
||||
|
||||
Requirements::javascript('sapphire/javascript/TableField.js');
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the headings on the template
|
||||
*
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
function Headings() {
|
||||
$i=0;
|
||||
foreach($this->fieldList as $fieldName => $fieldTitle) {
|
||||
$extraClass = "col".$i;
|
||||
$class = $this->fieldTypes[$fieldName];
|
||||
if(is_object($class)) $class = "";
|
||||
$class = $class." ".$extraClass;
|
||||
$headings[] = new ArrayData(array("Name" => $fieldName, "Title" => $fieldTitle, "Class" => $class));
|
||||
$i++;
|
||||
}
|
||||
return new DataObjectSet($headings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of columns needed for colspans
|
||||
* used in template
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function ItemCount() {
|
||||
return count($this->fieldList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the databased saved items, from DataObjects
|
||||
*
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
function sourceItems() {
|
||||
if($this->customSourceItems) {
|
||||
$items = $this->customSourceItems;
|
||||
} elseif($this->cachedSourceItems) {
|
||||
$items = $this->cachedSourceItems;
|
||||
} else {
|
||||
if(!empty($this->filterField) && intval($this->sourceFilter) > 0) {
|
||||
// Legacy: If a filterField is specified and the sourceFilter is a valid ID (old format)
|
||||
$SQL_filter = Convert::raw2sql($this->sourceFilter);
|
||||
$SQL_filterField = $this->filterField;
|
||||
$items = DataObject::get($this->sourceClass,"`$SQL_filterField` = '$SQL_filter'", $this->sourceSort, $this->sourceJoin);
|
||||
} else {
|
||||
// get query
|
||||
$dataQuery = $this->getQuery();
|
||||
// get data
|
||||
$records = $dataQuery->execute();
|
||||
$items = singleton($this->sourceClass)->buildDataObjectSet($records);
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the items from sourceItems using the encapsulation object
|
||||
*
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
function Items() {
|
||||
$output = new DataObjectSet();
|
||||
if($items = $this->sourceItems()) {
|
||||
foreach ($items as $item) {
|
||||
// Load the data in to a temporary form (for correct field types)
|
||||
$fieldset = $this->FieldSetForRow();
|
||||
if($fieldset){
|
||||
$form = new Form(null, null, $fieldset, new FieldSet());
|
||||
$form->loadDataFrom($item);
|
||||
// Add the item to our new DataObjectSet, with a wrapper class.
|
||||
$output->push(new TableField_Item($item, $this, $form, $this->fieldTypes));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create a temporary DataObject
|
||||
if($this->Can('add')) {
|
||||
if($this->wantDefaultAddRow){
|
||||
$output->push(new TableField_Item(null, $this, null, $this->fieldTypes, true));
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all fields for each row contained in the TableField.
|
||||
* Does not include the empty row.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function FieldSet() {
|
||||
$fields = array ();
|
||||
if($items = $this->sourceItems()) {
|
||||
foreach($items as $item) {
|
||||
// Load the data in to a temporary form (for correct field types)
|
||||
$fieldset = $this->FieldSetForRow();
|
||||
if ($fieldset)
|
||||
{
|
||||
// TODO Needs to be attached to a form existing in the DOM-tree
|
||||
$form = new Form($this, 'EditForm', $fieldset, new FieldSet());
|
||||
$row = new TableField_Item($item, $this, $form, $this->fieldTypes);
|
||||
$fields = array_merge($fields, $row->Fields()->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function FieldList() {
|
||||
return $this->fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Dataobjects contained in the field
|
||||
*/
|
||||
function saveInto(DataObject $record) {
|
||||
// CMS sometimes tries to set the value to one.
|
||||
if(is_array($this->value)){
|
||||
|
||||
// Sort into proper array
|
||||
$this->value = ArrayLib::invert($this->value);
|
||||
$dataObjects = $this->sortData($this->value, $record->ID);
|
||||
if($dataObjects['new']) {
|
||||
$newFields = $this->sortData($dataObjects['new'], $record->ID);
|
||||
}
|
||||
|
||||
$savedObj = $this->saveData($dataObjects, $this->editExisting);
|
||||
if($savedObj && $newFields) {
|
||||
$savedObj += $this->saveData($newFields,false);
|
||||
} else if($newFields) {
|
||||
$savedObj = $this->saveData($newFields,false);
|
||||
}
|
||||
$items = $this->sourceItems();
|
||||
FormResponse::update_dom_id($this->id(), $this->FieldHolder());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all fields in a single row.
|
||||
*
|
||||
* @return FieldSet
|
||||
*/
|
||||
function FieldSetForRow() {
|
||||
$fieldset = new FieldSet();
|
||||
if($this->fieldTypes){
|
||||
foreach($this->fieldTypes as $key => $fieldType) {
|
||||
if(isset($fieldType->class) && is_subclass_of($fieldType, 'FormField')) {
|
||||
// using clone, otherwise we would just add stuff to the same field-instance
|
||||
$field = clone $fieldType;
|
||||
} elseif(strpos($fieldType, '(') === false) {
|
||||
$field = new $fieldType($key);
|
||||
} else {
|
||||
$fieldName = $key;
|
||||
$fieldTitle = "";
|
||||
$field = eval("return new $fieldType;");
|
||||
}
|
||||
if($this->IsReadOnly || !$this->Can('edit')) {
|
||||
$field = $field->performReadonlyTransformation();
|
||||
}
|
||||
$fieldset->push($field);
|
||||
}
|
||||
}else{
|
||||
USER_ERROR("TableField::FieldSetForRow() - Fieldtypes were not specified",E_USER_WARNING);
|
||||
}
|
||||
|
||||
return $fieldset;
|
||||
}
|
||||
|
||||
function performReadonlyTransformation() {
|
||||
$this->permissions = array('show');
|
||||
return $this;
|
||||
}
|
||||
|
||||
function performDisabledTransformation() {
|
||||
$this->permissions = array('show');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed for Form->callfieldmethod.
|
||||
*/
|
||||
public function getField($fieldName, $combinedFieldName = null) {
|
||||
$fieldSet = $this->FieldSetForRow();
|
||||
$field = $fieldSet->dataFieldByName($fieldName);
|
||||
if(!$field) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($combinedFieldName) {
|
||||
$field->Name = $combinedFieldName;
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on save, it creates the appropriate objects and writes them
|
||||
* to the database.
|
||||
*/
|
||||
function saveData($dataObjects,$ExistingValues = true){
|
||||
$savedObj = array();
|
||||
$fieldset = $this->FieldSetForRow();
|
||||
|
||||
// add hiddenfields
|
||||
if($this->extraData) {
|
||||
foreach($this->extraData as $fieldName => $fieldValue) {
|
||||
$fieldset->push(new HiddenField($fieldName));
|
||||
}
|
||||
}
|
||||
|
||||
$form = new Form(null, null, $fieldset, new FieldSet());
|
||||
|
||||
if($dataObjects) {
|
||||
foreach ($dataObjects as $objectid => $fieldValues) {
|
||||
// we have to "sort" new data first, and process it in a seperate saveData-call (see setValue())
|
||||
if($objectid === "new") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// extra data was creating fields, but
|
||||
if($this->extraData) {
|
||||
$fieldValues = array_merge( $this->extraData, $fieldValues );
|
||||
}
|
||||
|
||||
$hasData = false;
|
||||
$obj = new $this->sourceClass();
|
||||
|
||||
if($ExistingValues) {
|
||||
$obj->ID = $objectid;
|
||||
}
|
||||
|
||||
// Legacy: Use the filter as a predefined relationship-ID
|
||||
if(!empty($this->filterField) && intval($this->sourceFilter) > 0) {
|
||||
$filterField = $this->filterField;
|
||||
$obj->$filterField = $this->sourceFilter;
|
||||
}
|
||||
|
||||
// Determine if there is changed data for saving
|
||||
$dataFields = array();
|
||||
|
||||
foreach($fieldValues as $type => $value) {
|
||||
if(is_array($this->extraData)){ // if the field is an actual datafield (not a preset hiddenfield)
|
||||
if(!in_array($type, array_keys($this->extraData))){
|
||||
$dataFields[$type] = $value;
|
||||
}
|
||||
}else{ // all fields are real
|
||||
$dataFields[$type] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$dataValues = ArrayLib::array_values_recursive($dataFields);
|
||||
|
||||
foreach($dataValues as $value) {
|
||||
if(!empty($value)) {
|
||||
$hasData = true;
|
||||
}
|
||||
}
|
||||
|
||||
// save
|
||||
if($hasData) {
|
||||
$form->loadDataFrom($fieldValues, true);
|
||||
$form->saveInto($obj);
|
||||
|
||||
$objectid = $obj->write();
|
||||
|
||||
$savedObj[$objectid] = "Updated";
|
||||
}
|
||||
|
||||
}
|
||||
return $savedObj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* organises the data in the appropriate manner for saving
|
||||
*/
|
||||
function sortData($data, $recordID = null) {
|
||||
if($data) {
|
||||
foreach($data as $field => $rowData) {
|
||||
$i = 0;
|
||||
$blank = 0;
|
||||
if(!is_array($rowData)) continue;
|
||||
foreach($rowData as $id => $value) {
|
||||
if($value == '$RecordID') $value = $recordID;
|
||||
|
||||
if($value){
|
||||
$dataObjects[$id][$field] = $value;
|
||||
}else{
|
||||
$blank++;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
// TODO ADD stuff for removing rows with incomplete data
|
||||
}
|
||||
}
|
||||
return $dataObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $extraData array
|
||||
*/
|
||||
function setExtraData($extraData) {
|
||||
$this->extraData = $extraData;
|
||||
}
|
||||
|
||||
function setWantDefaultAddRow($bool){
|
||||
$this->wantDefaultAddRow = $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function getExtraData() {
|
||||
return $this->extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the template to be rendered with
|
||||
*/
|
||||
function FieldHolder() {
|
||||
return $this->renderWith($this->template);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Int
|
||||
*/
|
||||
function sourceID() {
|
||||
return $this->filterField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String
|
||||
*/
|
||||
function delete() {
|
||||
$childId = Convert::raw2sql($_REQUEST['childID']);
|
||||
if (is_numeric($childId)) {
|
||||
$childObject = DataObject::get_by_id($this->sourceClass, $childId);
|
||||
if($childObject) {
|
||||
$childObject->delete();
|
||||
return 1;
|
||||
}
|
||||
}else{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function setTransformationConditions($conditions) {
|
||||
$this->transformationConditions = $conditions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validation
|
||||
*/
|
||||
|
||||
function jsValidation() {
|
||||
$js = "";
|
||||
|
||||
$fields = $this->FieldSet();
|
||||
// TODO doesn't automatically update validation when adding a row
|
||||
foreach($fields as $field) {
|
||||
//if the field type has some special specific specification for validation of itself
|
||||
$js .= $field->jsValidation();
|
||||
}
|
||||
|
||||
// TODO Implement custom requiredFields
|
||||
if($this->requiredFields) {
|
||||
foreach ($this->requiredFields as $field) {
|
||||
if($fields->dataFieldByName($field)) {
|
||||
$js .= "\t\t\t\t\trequire('$field');\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $js;
|
||||
}
|
||||
|
||||
function php($data) {
|
||||
$valid = true;
|
||||
if($items = $this->sourceItems()) {
|
||||
foreach($items as $item) {
|
||||
// Load the data in to a temporary form (for correct field types)
|
||||
$fieldset = $this->FieldSetForRow();
|
||||
if ($fieldset)
|
||||
{
|
||||
$form = new Form(null, null, $fieldset, new FieldSet());
|
||||
$row = new TableField_Item($item, $this, $form, $this->fieldTypes);
|
||||
$fields = array_merge($fields, $row->Fields()->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
$fields = new FieldSet($fields);
|
||||
|
||||
foreach($fields as $field) {
|
||||
$valid = ($field->validate($this) && $valid);
|
||||
}
|
||||
|
||||
if($this->requiredFields) {
|
||||
foreach($this->requiredFields as $field) {
|
||||
if($fields->dataFieldByName($field) && !$data[$field]) {
|
||||
$this->validationError($field,'"' . strip_tags($field) . '" is required',"required");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function setRequiredFields($fields) {
|
||||
$this->requiredFields = $fields;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* encapsulation object for the table field. it stores the dataobject,
|
||||
* and nessecary encapsulation fields
|
||||
*/
|
||||
class TableField_Item extends TableListField_Item {
|
||||
|
||||
protected $fields;
|
||||
|
||||
protected $data;
|
||||
|
||||
protected $fieldTypes;
|
||||
|
||||
protected $isAddRow;
|
||||
|
||||
protected $extraData;
|
||||
|
||||
/**
|
||||
* Each row contains a dataobject with any number of attributes
|
||||
* @param $ID int The ID of the record
|
||||
* @param $form Form A Form object containing all of the fields for this item. The data should be loaded in
|
||||
* @param $fieldTypes array An array of name => fieldtype for use when creating a new field
|
||||
* @param $parent TableListField The parent table for quick reference of names, and id's for storing values.
|
||||
*/
|
||||
function __construct($item = null, $parent, $form, $fieldTypes, $isAddRow = false) {
|
||||
$this->data = $form;
|
||||
$this->fieldTypes = $fieldTypes;
|
||||
$this->isAddRow = $isAddRow;
|
||||
$this->item = $item;
|
||||
|
||||
parent::__construct(($this->item) ? $this->item : new DataObject(), $parent);
|
||||
|
||||
$this->fields = $this->createFields();
|
||||
}
|
||||
/**
|
||||
* Represents each cell of the table with an attribute
|
||||
*/
|
||||
function createFields() {
|
||||
// Existing record
|
||||
if($this->item && $this->data) {
|
||||
$form = $this->data;
|
||||
$this->fieldset = $form->Fields();
|
||||
if($this->fieldset) {
|
||||
$i=0;
|
||||
foreach($this->fieldset as $field) {
|
||||
$origFieldName = $field->Name();
|
||||
|
||||
// set unique fieldname with id
|
||||
$combinedFieldName = $this->parent->Name() . "[" . $this->ID . "][" . $origFieldName . "]";
|
||||
if($this->isAddRow) $combinedFieldName .= '[]';
|
||||
|
||||
// get value
|
||||
if(strpos($origFieldName,'.') === false) {
|
||||
$value = $field->dataValue();
|
||||
} else {
|
||||
// this supports the syntax fieldName = Relation.RelatedField
|
||||
$fieldNameParts = explode('.', $origFieldName) ;
|
||||
$tmpItem = $this->item;
|
||||
for($j=0;$j<sizeof($fieldNameParts);$j++) {
|
||||
$relationMethod = $fieldNameParts[$j];
|
||||
$idField = $relationMethod . 'ID';
|
||||
if($j == sizeof($fieldNameParts)-1) {
|
||||
$value = $tmpItem->$relationMethod;
|
||||
} else {
|
||||
$tmpItem = $tmpItem->$relationMethod();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$field->Name = $combinedFieldName;
|
||||
$field->setValue($field->dataValue());
|
||||
$field->setExtraClass('col'.$i);
|
||||
$field->setForm($this->data);
|
||||
|
||||
// transformation
|
||||
if(isset($this->parent->transformationConditions[$origFieldName])) {
|
||||
$transformation = $this->parent->transformationConditions[$origFieldName]['transformation'];
|
||||
$rule = str_replace("\$","\$this->item->", $this->parent->transformationConditions[$origFieldName]['rule']);
|
||||
$ruleApplies = null;
|
||||
eval('$ruleApplies = ('.$rule.');');
|
||||
if($ruleApplies) {
|
||||
$field = $field->$transformation();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// formatting
|
||||
$item = $this->item;
|
||||
$value = $field->Value();
|
||||
if(array_key_exists($origFieldName, $this->parent->fieldFormatting)) {
|
||||
$format = str_replace('$value', "__VAL__", $this->parent->fieldFormatting[$origFieldName]);
|
||||
$format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
|
||||
$format = str_replace('__VAL__', '$value', $format);
|
||||
eval('$value = "' . $format . '";');
|
||||
$field->dontEscape = true;
|
||||
$field->setValue($value);
|
||||
}
|
||||
|
||||
$this->fields[] = $field;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
// New record
|
||||
} else {
|
||||
$list = $this->parent->FieldList();
|
||||
$i=0;
|
||||
foreach($list as $fieldName => $fieldTitle) {
|
||||
if(strpos($fieldName, ".")) {
|
||||
$shortFieldName = substr($fieldName, strpos($fieldName, ".")+1, strlen($fieldName));
|
||||
} else {
|
||||
$shortFieldName = $fieldName;
|
||||
}
|
||||
$combinedFieldName = $this->parent->Name() . "[new][" . $shortFieldName . "][]";
|
||||
$fieldType = $this->fieldTypes[$fieldName];
|
||||
if(isset($fieldType->class) && is_subclass_of($fieldType, 'FormField')) {
|
||||
$field = clone $fieldType; // we can't use the same instance all over, as we change names
|
||||
$field->Name = $combinedFieldName;
|
||||
} elseif(strpos($fieldType, '(') === false) {
|
||||
//echo ("<li>Type: ".$fieldType." fieldName: ". $filedName. " Title: ".$fieldTitle."</li>");
|
||||
$field = new $fieldType($combinedFieldName,$fieldTitle);
|
||||
} else {
|
||||
$field = eval("return new " . $fieldType . ";");
|
||||
|
||||
}
|
||||
$field->setExtraClass('col'.$i);
|
||||
$this->fields[] = $field;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
return new DataObjectSet($this->fields);
|
||||
}
|
||||
|
||||
function Fields() {
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
function ExtraData() {
|
||||
$content = "";
|
||||
$id = ($this->item->ID) ? $this->item->ID : "new";
|
||||
if($this->parent->getExtraData()) {
|
||||
foreach($this->parent->getExtraData() as $fieldName=>$fieldValue) {
|
||||
$name = $this->parent->Name() . "[" . $id . "][" . $fieldName . "]";
|
||||
if($this->isAddRow) $name .= '[]';
|
||||
$field = new HiddenField($name, null, $fieldValue);
|
||||
$content .= $field->FieldHolder() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
function Can($mode) {
|
||||
return $this->parent->Can($mode);
|
||||
}
|
||||
|
||||
function Parent() {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the base link for the call below.
|
||||
*/
|
||||
function BaseLink() {
|
||||
$parent = $this->parent;
|
||||
$action = $parent->FormAction();
|
||||
if(substr($action, -1, 1) !== '&'){
|
||||
$action = $action."&";
|
||||
}
|
||||
$action = str_replace('&', '&', $action);
|
||||
|
||||
return $action . "action_callfieldmethod=1&fieldName=". $parent->Name() . "&childID=" . $this->ID;
|
||||
}
|
||||
/**
|
||||
* Runs the delete() method on the Tablefield parent.
|
||||
* Allows the deletion of objects via ajax
|
||||
*/
|
||||
function DeleteLink() {
|
||||
return $this->BaseLink() . "&methodName=delete";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
<?php
|
||||
|
||||
/**
|
||||
* TableField behaves in the same manner as TableListField, however allows the addition of
|
||||
* fields and editing of attributes specified, and filtering results.
|
||||
*
|
||||
* Caution: If you insert DropdownFields in the fieldTypes-array, make sure they have an empty first option.
|
||||
* Otherwise the saving can't determine if a new row should really be saved.
|
||||
*
|
||||
* Caution: TableField relies on {@FormResponse} to reload the field after it is saved.
|
||||
* A TableField-instance should never be saved twice without reloading, because otherwise it
|
||||
* can't determine if a field is new (=create) or existing (=update), and will produce duplicates.
|
||||
*
|
||||
* @param $name string The fieldname
|
||||
* @param $sourceClass string The source class of this field
|
||||
* @param $fieldList array An array of field headings of Fieldname => Heading Text (eg. heading1)
|
||||
* @param $fieldTypes array An array of field types of fieldname => fieldType (eg. formfield). Do not use for extra data/hiddenfields.
|
||||
* @param $filterField string DEPRECATED The actual limiting filter, eg. 1 (please use $sourceFilter in the form "ParentID = 1" instead)
|
||||
* @param $sourceFilter string The filter you wish to limit the objects by
|
||||
* @param $editExisting boolean (Note: Has to stay on this position for legacy reasons)
|
||||
* @param $sourceSort string
|
||||
* @param $sourceJoin string
|
||||
*
|
||||
* TODO We should refactor this to support a single FieldSet instead of evaluated Strings for building FormFields
|
||||
*/
|
||||
|
||||
class TableField extends TableListField {
|
||||
|
||||
protected $sourceClass;
|
||||
|
||||
protected $sourceFilter;
|
||||
|
||||
protected $fieldList;
|
||||
|
||||
/**
|
||||
* @var $fieldTypes FieldSet
|
||||
* Caution: Use {@setExtraData()} instead of manually adding HiddenFields if you want to
|
||||
* preset relations or other default data.
|
||||
*/
|
||||
protected $fieldTypes;
|
||||
|
||||
protected $sourceSort;
|
||||
|
||||
protected $sourceJoin;
|
||||
|
||||
/**
|
||||
* @var $template string Template-Overrides
|
||||
*/
|
||||
protected $template = "TableField";
|
||||
|
||||
/**
|
||||
* @var $extraData array Any extra data that need to be included, e.g. to retain
|
||||
* has-many relations. Format: array('FieldName' => 'Value')
|
||||
*/
|
||||
protected $extraData;
|
||||
|
||||
protected $tempForm;
|
||||
|
||||
/**
|
||||
* Influence output without having to subclass the template.
|
||||
*/
|
||||
protected $permissions = array(
|
||||
"edit",
|
||||
"delete",
|
||||
"add",
|
||||
//"export",
|
||||
);
|
||||
|
||||
public $transformationConditions = array();
|
||||
|
||||
/**
|
||||
* @var $requiredFields array Required fields as a numerical array.
|
||||
* Please use an instance of Validator on the including
|
||||
* form.
|
||||
*/
|
||||
protected $requiredFields = null;
|
||||
|
||||
/**
|
||||
* For some table, we want Can(add) to be true, so we can add,
|
||||
* but we don't want the default add row to be presented in the
|
||||
* table, we can set this wantDefaultAddRow to be false.
|
||||
* @param boolean $wantDefaultAddRow
|
||||
*/
|
||||
protected $wantDefaultAddRow = true;
|
||||
|
||||
function __construct($name, $sourceClass, $fieldList, $fieldTypes, $filterField = null,
|
||||
$sourceFilter = null, $editExisting = true, $sourceSort = null, $sourceJoin = null) {
|
||||
|
||||
$this->fieldTypes = $fieldTypes;
|
||||
$this->filterField = $filterField;
|
||||
|
||||
$this->editExisting = $editExisting;
|
||||
parent::__construct($name, $sourceClass, $fieldList, $sourceFilter, $sourceSort, $sourceJoin);
|
||||
|
||||
Requirements::javascript('sapphire/javascript/TableField.js');
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the headings on the template
|
||||
*
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
function Headings() {
|
||||
$i=0;
|
||||
foreach($this->fieldList as $fieldName => $fieldTitle) {
|
||||
$extraClass = "col".$i;
|
||||
$class = $this->fieldTypes[$fieldName];
|
||||
if(is_object($class)) $class = "";
|
||||
$class = $class." ".$extraClass;
|
||||
$headings[] = new ArrayData(array("Name" => $fieldName, "Title" => $fieldTitle, "Class" => $class));
|
||||
$i++;
|
||||
}
|
||||
return new DataObjectSet($headings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the number of columns needed for colspans
|
||||
* used in template
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function ItemCount() {
|
||||
return count($this->fieldList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the databased saved items, from DataObjects
|
||||
*
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
function sourceItems() {
|
||||
if($this->customSourceItems) {
|
||||
$items = $this->customSourceItems;
|
||||
} elseif($this->cachedSourceItems) {
|
||||
$items = $this->cachedSourceItems;
|
||||
} else {
|
||||
if(!empty($this->filterField) && intval($this->sourceFilter) > 0) {
|
||||
// Legacy: If a filterField is specified and the sourceFilter is a valid ID (old format)
|
||||
$SQL_filter = Convert::raw2sql($this->sourceFilter);
|
||||
$SQL_filterField = $this->filterField;
|
||||
$items = DataObject::get($this->sourceClass,"`$SQL_filterField` = '$SQL_filter'", $this->sourceSort, $this->sourceJoin);
|
||||
} else {
|
||||
// get query
|
||||
$dataQuery = $this->getQuery();
|
||||
// get data
|
||||
$records = $dataQuery->execute();
|
||||
$items = singleton($this->sourceClass)->buildDataObjectSet($records);
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the items from sourceItems using the encapsulation object
|
||||
*
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
function Items() {
|
||||
$output = new DataObjectSet();
|
||||
if($items = $this->sourceItems()) {
|
||||
foreach ($items as $item) {
|
||||
// Load the data in to a temporary form (for correct field types)
|
||||
$fieldset = $this->FieldSetForRow();
|
||||
if($fieldset){
|
||||
$form = new Form(null, null, $fieldset, new FieldSet());
|
||||
$form->loadDataFrom($item);
|
||||
// Add the item to our new DataObjectSet, with a wrapper class.
|
||||
$output->push(new TableField_Item($item, $this, $form, $this->fieldTypes));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create a temporary DataObject
|
||||
if($this->Can('add')) {
|
||||
if($this->wantDefaultAddRow){
|
||||
$output->push(new TableField_Item(null, $this, null, $this->fieldTypes, true));
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all fields for each row contained in the TableField.
|
||||
* Does not include the empty row.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function FieldSet() {
|
||||
$fields = array ();
|
||||
if($items = $this->sourceItems()) {
|
||||
foreach($items as $item) {
|
||||
// Load the data in to a temporary form (for correct field types)
|
||||
$fieldset = $this->FieldSetForRow();
|
||||
if ($fieldset)
|
||||
{
|
||||
// TODO Needs to be attached to a form existing in the DOM-tree
|
||||
$form = new Form($this, 'EditForm', $fieldset, new FieldSet());
|
||||
$row = new TableField_Item($item, $this, $form, $this->fieldTypes);
|
||||
$fields = array_merge($fields, $row->Fields()->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function FieldList() {
|
||||
return $this->fieldList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the Dataobjects contained in the field
|
||||
*/
|
||||
function saveInto(DataObject $record) {
|
||||
// CMS sometimes tries to set the value to one.
|
||||
if(is_array($this->value)){
|
||||
|
||||
// Sort into proper array
|
||||
$this->value = ArrayLib::invert($this->value);
|
||||
$dataObjects = $this->sortData($this->value, $record->ID);
|
||||
if($dataObjects['new']) {
|
||||
$newFields = $this->sortData($dataObjects['new'], $record->ID);
|
||||
}
|
||||
|
||||
$savedObj = $this->saveData($dataObjects, $this->editExisting);
|
||||
if($savedObj && $newFields) {
|
||||
$savedObj += $this->saveData($newFields,false);
|
||||
} else if($newFields) {
|
||||
$savedObj = $this->saveData($newFields,false);
|
||||
}
|
||||
$items = $this->sourceItems();
|
||||
FormResponse::update_dom_id($this->id(), $this->FieldHolder());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all fields in a single row.
|
||||
*
|
||||
* @return FieldSet
|
||||
*/
|
||||
function FieldSetForRow() {
|
||||
$fieldset = new FieldSet();
|
||||
if($this->fieldTypes){
|
||||
foreach($this->fieldTypes as $key => $fieldType) {
|
||||
if(isset($fieldType->class) && is_subclass_of($fieldType, 'FormField')) {
|
||||
// using clone, otherwise we would just add stuff to the same field-instance
|
||||
$field = clone $fieldType;
|
||||
} elseif(strpos($fieldType, '(') === false) {
|
||||
$field = new $fieldType($key);
|
||||
} else {
|
||||
$fieldName = $key;
|
||||
$fieldTitle = "";
|
||||
$field = eval("return new $fieldType;");
|
||||
}
|
||||
if($this->IsReadOnly || !$this->Can('edit')) {
|
||||
$field = $field->performReadonlyTransformation();
|
||||
}
|
||||
$fieldset->push($field);
|
||||
}
|
||||
}else{
|
||||
USER_ERROR("TableField::FieldSetForRow() - Fieldtypes were not specified",E_USER_WARNING);
|
||||
}
|
||||
|
||||
return $fieldset;
|
||||
}
|
||||
|
||||
function performReadonlyTransformation() {
|
||||
$this->permissions = array('show');
|
||||
return $this;
|
||||
}
|
||||
|
||||
function performDisabledTransformation() {
|
||||
$this->permissions = array('show');
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Needed for Form->callfieldmethod.
|
||||
*/
|
||||
public function getField($fieldName, $combinedFieldName = null) {
|
||||
$fieldSet = $this->FieldSetForRow();
|
||||
$field = $fieldSet->dataFieldByName($fieldName);
|
||||
if(!$field) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($combinedFieldName) {
|
||||
$field->Name = $combinedFieldName;
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on save, it creates the appropriate objects and writes them
|
||||
* to the database.
|
||||
*/
|
||||
function saveData($dataObjects,$ExistingValues = true){
|
||||
$savedObj = array();
|
||||
$fieldset = $this->FieldSetForRow();
|
||||
|
||||
// add hiddenfields
|
||||
if($this->extraData) {
|
||||
foreach($this->extraData as $fieldName => $fieldValue) {
|
||||
$fieldset->push(new HiddenField($fieldName));
|
||||
}
|
||||
}
|
||||
|
||||
$form = new Form(null, null, $fieldset, new FieldSet());
|
||||
|
||||
if($dataObjects) {
|
||||
foreach ($dataObjects as $objectid => $fieldValues) {
|
||||
// we have to "sort" new data first, and process it in a seperate saveData-call (see setValue())
|
||||
if($objectid === "new") {
|
||||
continue;
|
||||
}
|
||||
|
||||
// extra data was creating fields, but
|
||||
if($this->extraData) {
|
||||
$fieldValues = array_merge( $this->extraData, $fieldValues );
|
||||
}
|
||||
|
||||
$hasData = false;
|
||||
$obj = new $this->sourceClass();
|
||||
|
||||
if($ExistingValues) {
|
||||
$obj->ID = $objectid;
|
||||
}
|
||||
|
||||
// Legacy: Use the filter as a predefined relationship-ID
|
||||
if(!empty($this->filterField) && intval($this->sourceFilter) > 0) {
|
||||
$filterField = $this->filterField;
|
||||
$obj->$filterField = $this->sourceFilter;
|
||||
}
|
||||
|
||||
// Determine if there is changed data for saving
|
||||
$dataFields = array();
|
||||
|
||||
foreach($fieldValues as $type => $value) {
|
||||
if(is_array($this->extraData)){ // if the field is an actual datafield (not a preset hiddenfield)
|
||||
if(!in_array($type, array_keys($this->extraData))){
|
||||
$dataFields[$type] = $value;
|
||||
}
|
||||
}else{ // all fields are real
|
||||
$dataFields[$type] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$dataValues = ArrayLib::array_values_recursive($dataFields);
|
||||
|
||||
foreach($dataValues as $value) {
|
||||
if(!empty($value)) {
|
||||
$hasData = true;
|
||||
}
|
||||
}
|
||||
|
||||
// save
|
||||
if($hasData) {
|
||||
$form->loadDataFrom($fieldValues, true);
|
||||
$form->saveInto($obj);
|
||||
|
||||
$objectid = $obj->write();
|
||||
|
||||
$savedObj[$objectid] = "Updated";
|
||||
}
|
||||
|
||||
}
|
||||
return $savedObj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* organises the data in the appropriate manner for saving
|
||||
*/
|
||||
function sortData($data, $recordID = null) {
|
||||
if($data) {
|
||||
foreach($data as $field => $rowData) {
|
||||
$i = 0;
|
||||
$blank = 0;
|
||||
if(!is_array($rowData)) continue;
|
||||
foreach($rowData as $id => $value) {
|
||||
if($value == '$RecordID') $value = $recordID;
|
||||
|
||||
if($value){
|
||||
$dataObjects[$id][$field] = $value;
|
||||
}else{
|
||||
$blank++;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
// TODO ADD stuff for removing rows with incomplete data
|
||||
}
|
||||
}
|
||||
return $dataObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $extraData array
|
||||
*/
|
||||
function setExtraData($extraData) {
|
||||
$this->extraData = $extraData;
|
||||
}
|
||||
|
||||
function setWantDefaultAddRow($bool){
|
||||
$this->wantDefaultAddRow = $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
function getExtraData() {
|
||||
return $this->extraData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the template to be rendered with
|
||||
*/
|
||||
function FieldHolder() {
|
||||
return $this->renderWith($this->template);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Int
|
||||
*/
|
||||
function sourceID() {
|
||||
return $this->filterField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return String
|
||||
*/
|
||||
function delete() {
|
||||
$childId = Convert::raw2sql($_REQUEST['childID']);
|
||||
if (is_numeric($childId)) {
|
||||
$childObject = DataObject::get_by_id($this->sourceClass, $childId);
|
||||
if($childObject) {
|
||||
$childObject->delete();
|
||||
return 1;
|
||||
}
|
||||
}else{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
function setTransformationConditions($conditions) {
|
||||
$this->transformationConditions = $conditions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validation
|
||||
*/
|
||||
|
||||
function jsValidation() {
|
||||
$js = "";
|
||||
|
||||
$fields = $this->FieldSet();
|
||||
// TODO doesn't automatically update validation when adding a row
|
||||
foreach($fields as $field) {
|
||||
//if the field type has some special specific specification for validation of itself
|
||||
$js .= $field->jsValidation();
|
||||
}
|
||||
|
||||
// TODO Implement custom requiredFields
|
||||
if($this->requiredFields) {
|
||||
foreach ($this->requiredFields as $field) {
|
||||
if($fields->dataFieldByName($field)) {
|
||||
$js .= "\t\t\t\t\trequire('$field');\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $js;
|
||||
}
|
||||
|
||||
function php($data) {
|
||||
$valid = true;
|
||||
if($items = $this->sourceItems()) {
|
||||
foreach($items as $item) {
|
||||
// Load the data in to a temporary form (for correct field types)
|
||||
$fieldset = $this->FieldSetForRow();
|
||||
if ($fieldset)
|
||||
{
|
||||
$form = new Form(null, null, $fieldset, new FieldSet());
|
||||
$row = new TableField_Item($item, $this, $form, $this->fieldTypes);
|
||||
$fields = array_merge($fields, $row->Fields()->toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
$fields = new FieldSet($fields);
|
||||
|
||||
foreach($fields as $field) {
|
||||
$valid = ($field->validate($this) && $valid);
|
||||
}
|
||||
|
||||
if($this->requiredFields) {
|
||||
foreach($this->requiredFields as $field) {
|
||||
if($fields->dataFieldByName($field) && !$data[$field]) {
|
||||
$this->validationError($field,'"' . strip_tags($field) . '" is required',"required");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function setRequiredFields($fields) {
|
||||
$this->requiredFields = $fields;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* encapsulation object for the table field. it stores the dataobject,
|
||||
* and nessecary encapsulation fields
|
||||
*/
|
||||
class TableField_Item extends TableListField_Item {
|
||||
|
||||
protected $fields;
|
||||
|
||||
protected $data;
|
||||
|
||||
protected $fieldTypes;
|
||||
|
||||
protected $isAddRow;
|
||||
|
||||
protected $extraData;
|
||||
|
||||
/**
|
||||
* Each row contains a dataobject with any number of attributes
|
||||
* @param $ID int The ID of the record
|
||||
* @param $form Form A Form object containing all of the fields for this item. The data should be loaded in
|
||||
* @param $fieldTypes array An array of name => fieldtype for use when creating a new field
|
||||
* @param $parent TableListField The parent table for quick reference of names, and id's for storing values.
|
||||
*/
|
||||
function __construct($item = null, $parent, $form, $fieldTypes, $isAddRow = false) {
|
||||
$this->data = $form;
|
||||
$this->fieldTypes = $fieldTypes;
|
||||
$this->isAddRow = $isAddRow;
|
||||
$this->item = $item;
|
||||
|
||||
parent::__construct(($this->item) ? $this->item : new DataObject(), $parent);
|
||||
|
||||
$this->fields = $this->createFields();
|
||||
}
|
||||
/**
|
||||
* Represents each cell of the table with an attribute
|
||||
*/
|
||||
function createFields() {
|
||||
// Existing record
|
||||
if($this->item && $this->data) {
|
||||
$form = $this->data;
|
||||
$this->fieldset = $form->Fields();
|
||||
if($this->fieldset) {
|
||||
$i=0;
|
||||
foreach($this->fieldset as $field) {
|
||||
$origFieldName = $field->Name();
|
||||
|
||||
// set unique fieldname with id
|
||||
$combinedFieldName = $this->parent->Name() . "[" . $this->ID . "][" . $origFieldName . "]";
|
||||
if($this->isAddRow) $combinedFieldName .= '[]';
|
||||
|
||||
// get value
|
||||
if(strpos($origFieldName,'.') === false) {
|
||||
$value = $field->dataValue();
|
||||
} else {
|
||||
// this supports the syntax fieldName = Relation.RelatedField
|
||||
$fieldNameParts = explode('.', $origFieldName) ;
|
||||
$tmpItem = $this->item;
|
||||
for($j=0;$j<sizeof($fieldNameParts);$j++) {
|
||||
$relationMethod = $fieldNameParts[$j];
|
||||
$idField = $relationMethod . 'ID';
|
||||
if($j == sizeof($fieldNameParts)-1) {
|
||||
$value = $tmpItem->$relationMethod;
|
||||
} else {
|
||||
$tmpItem = $tmpItem->$relationMethod();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$field->Name = $combinedFieldName;
|
||||
$field->setValue($field->dataValue());
|
||||
$field->setExtraClass('col'.$i);
|
||||
$field->setForm($this->data);
|
||||
|
||||
// transformation
|
||||
if(isset($this->parent->transformationConditions[$origFieldName])) {
|
||||
$transformation = $this->parent->transformationConditions[$origFieldName]['transformation'];
|
||||
$rule = str_replace("\$","\$this->item->", $this->parent->transformationConditions[$origFieldName]['rule']);
|
||||
$ruleApplies = null;
|
||||
eval('$ruleApplies = ('.$rule.');');
|
||||
if($ruleApplies) {
|
||||
$field = $field->$transformation();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// formatting
|
||||
$item = $this->item;
|
||||
$value = $field->Value();
|
||||
if(array_key_exists($origFieldName, $this->parent->fieldFormatting)) {
|
||||
$format = str_replace('$value', "__VAL__", $this->parent->fieldFormatting[$origFieldName]);
|
||||
$format = preg_replace('/\$([A-Za-z0-9-_]+)/','$item->$1', $format);
|
||||
$format = str_replace('__VAL__', '$value', $format);
|
||||
eval('$value = "' . $format . '";');
|
||||
$field->dontEscape = true;
|
||||
$field->setValue($value);
|
||||
}
|
||||
|
||||
$this->fields[] = $field;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
// New record
|
||||
} else {
|
||||
$list = $this->parent->FieldList();
|
||||
$i=0;
|
||||
foreach($list as $fieldName => $fieldTitle) {
|
||||
if(strpos($fieldName, ".")) {
|
||||
$shortFieldName = substr($fieldName, strpos($fieldName, ".")+1, strlen($fieldName));
|
||||
} else {
|
||||
$shortFieldName = $fieldName;
|
||||
}
|
||||
$combinedFieldName = $this->parent->Name() . "[new][" . $shortFieldName . "][]";
|
||||
$fieldType = $this->fieldTypes[$fieldName];
|
||||
if(isset($fieldType->class) && is_subclass_of($fieldType, 'FormField')) {
|
||||
$field = clone $fieldType; // we can't use the same instance all over, as we change names
|
||||
$field->Name = $combinedFieldName;
|
||||
} elseif(strpos($fieldType, '(') === false) {
|
||||
//echo ("<li>Type: ".$fieldType." fieldName: ". $filedName. " Title: ".$fieldTitle."</li>");
|
||||
$field = new $fieldType($combinedFieldName,$fieldTitle);
|
||||
} else {
|
||||
$field = eval("return new " . $fieldType . ";");
|
||||
|
||||
}
|
||||
$field->setExtraClass('col'.$i);
|
||||
$this->fields[] = $field;
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
return new DataObjectSet($this->fields);
|
||||
}
|
||||
|
||||
function Fields() {
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
function ExtraData() {
|
||||
$content = "";
|
||||
$id = ($this->item->ID) ? $this->item->ID : "new";
|
||||
if($this->parent->getExtraData()) {
|
||||
foreach($this->parent->getExtraData() as $fieldName=>$fieldValue) {
|
||||
$name = $this->parent->Name() . "[" . $id . "][" . $fieldName . "]";
|
||||
if($this->isAddRow) $name .= '[]';
|
||||
$field = new HiddenField($name, null, $fieldValue);
|
||||
$content .= $field->FieldHolder() . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
function Can($mode) {
|
||||
return $this->parent->Can($mode);
|
||||
}
|
||||
|
||||
function Parent() {
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the base link for the call below.
|
||||
*/
|
||||
function BaseLink() {
|
||||
$parent = $this->parent;
|
||||
$action = $parent->FormAction();
|
||||
if(substr($action, -1, 1) !== '&'){
|
||||
$action = $action."&";
|
||||
}
|
||||
$action = str_replace('&', '&', $action);
|
||||
|
||||
return $action . "action_callfieldmethod=1&fieldName=". $parent->Name() . "&childID=" . $this->ID;
|
||||
}
|
||||
/**
|
||||
* Runs the delete() method on the Tablefield parent.
|
||||
* Allows the deletion of objects via ajax
|
||||
*/
|
||||
function DeleteLink() {
|
||||
return $this->BaseLink() . "&methodName=delete";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
@ -1,12 +1,12 @@
|
||||
Behaviour.register({
|
||||
'input#Form_EditForm_ExternalURL': {
|
||||
onclick: function() {
|
||||
$('Form_EditForm_RedirectionType_External').checked = true;
|
||||
}
|
||||
},
|
||||
'#TreeDropdownField_Form_EditForm_LinkToID': {
|
||||
onclick: function() {
|
||||
$('Form_EditForm_RedirectionType_Internal').checked = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
Behaviour.register({
|
||||
'input#Form_EditForm_ExternalURL': {
|
||||
onclick: function() {
|
||||
$('Form_EditForm_RedirectionType_External').checked = true;
|
||||
}
|
||||
},
|
||||
'#TreeDropdownField_Form_EditForm_LinkToID': {
|
||||
onclick: function() {
|
||||
$('Form_EditForm_RedirectionType_Internal').checked = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,204 +1,204 @@
|
||||
<?php
|
||||
/**
|
||||
* TODO Investigate whether SSViewer will be fast enough to handle hundreds of little template files.
|
||||
*
|
||||
* A better (more SS) way of doing the HTML code in here is to place them all in small template files
|
||||
* (eg. BBCodeParser_Code.ss contains the HTML for BBCodeParser::parseCode()), but the overhead this
|
||||
* would cause is likely to make this very unusable, as nice as it would be.
|
||||
*/
|
||||
class BBCodeParser extends TextParser {
|
||||
static function usable_tags() {
|
||||
return new DataObjectSet(
|
||||
new ArrayData(array(
|
||||
"Title" => "Bold Text",
|
||||
"Example" => "[b]<b>Bold</b>[/b]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Italic Text",
|
||||
"Example" => "[i]<i>Italics</i>[/i]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Underlined Text",
|
||||
"Example" => "[u]<u>Underlined</u>[/u]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Struck-out Text",
|
||||
"Example" => "[s]<s>Struck-out</s>[/s]"
|
||||
)),
|
||||
|
||||
new ArrayData(array(
|
||||
"Title" => "Website link",
|
||||
"Description" => "Link to another website or URL",
|
||||
"Example" => "[url]http://www.website.com/[/url]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Website link",
|
||||
"Description" => "Link to another website or URL",
|
||||
"Example" => "[url=http://www.website.com/]Some website[/url]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Email link",
|
||||
"Description" => "Create link to an email address",
|
||||
"Example" => "[email]you@yoursite.com[/email]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Email link",
|
||||
"Description" => "Create link to an email address",
|
||||
"Example" => "[email=you@yoursite.com]email me[/email]"
|
||||
)),
|
||||
|
||||
new ArrayData(array(
|
||||
"Title" => "Image",
|
||||
"Description" => "Show an image in your post",
|
||||
"Example" => "[img]http://www.website.com/image.jpg[/img]"
|
||||
)),
|
||||
|
||||
new ArrayData(array(
|
||||
"Title" => "Code Block",
|
||||
"Description" => "Unformatted code block",
|
||||
"Example" => "[code]Code block[/code]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "HTML Code Block",
|
||||
"Description" => "HTML-formatted code block",
|
||||
"Example" => "[html]HTML code block[/html]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "HTML Code Block",
|
||||
"Description" => "HTML-formatted code block",
|
||||
"Example" => "[code html]HTML code block[/code]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "PHP Code Block",
|
||||
"Description" => "PHP-formatted code block",
|
||||
"Example" => "[php]PHP code block[/php]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "PHP Code Block",
|
||||
"Description" => "PHP-formatted code block",
|
||||
"Example" => "[code php]PHP code block[/code]"
|
||||
))
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
function useable_tagsHTML(){
|
||||
$useabletags = "<ul class='bbcodeExamples'>";
|
||||
foreach($this->usable_tags()->toArray() as $tag){
|
||||
$useabletags = $useabletags."<li>".$tag->Example."</li>";
|
||||
}
|
||||
return $useabletags."</ul>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Meat of the class - finds tags to parse and offloads the parsing to member methods.
|
||||
*
|
||||
* parse() will look at various [square-bracketed] tags and replace them.
|
||||
* Complicated replacements are handled via private member methods.
|
||||
* Simple stuff is done inline inside the preg_replace to keep overhead low.
|
||||
*/
|
||||
function parse() {
|
||||
$this->content = str_replace(array('&', '<', '>'), array('&', '<', '>'), $this->content);
|
||||
|
||||
// Parse [code X] and base [code] tags
|
||||
$this->content = preg_replace("#\[code (.+?)\](.+?)\[/code\]#ies", "\$this->parseCode('\\2', '\\1')", $this->content);
|
||||
$this->content = preg_replace("#\[code\](.+?)\[/code\]#ies", "\$this->parseCode('\\1')", $this->content);
|
||||
|
||||
// Parse [html] and [php] tags (Shorthand [code html] and [code php] respectively)
|
||||
$this->content = preg_replace("#\[html\](.+?)\[/html\]#ies", "\$this->parseCode('\\1', 'html')", $this->content);
|
||||
$this->content = preg_replace("#\[php\](.+?)\[/php\]#ies", "\$this->parseCode('\\1', 'php')", $this->content);
|
||||
|
||||
// Simple HTML tags (bold, italic, underline, strike-through) - No need to call member methods for these
|
||||
$this->content = preg_replace("#\[b\](.+?)\[/b\]#is", "<strong>\\1</strong>", $this->content);
|
||||
$this->content = preg_replace("#\[i\](.+?)\[/i\]#is", "<em>\\1</em>", $this->content);
|
||||
$this->content = preg_replace("#\[u\](.+?)\[/u\]#is", "<u>\\1</u>", $this->content);
|
||||
$this->content = preg_replace("#\[s\](.+?)\[/s\]#is", "<s>\\1</s>", $this->content);
|
||||
|
||||
// Email tags ([email]name@domain.tld[/email] and [email=name@domain.tld]email me![/email])
|
||||
$this->content = preg_replace("#\[email\](\S+?)\[/email\]#i", "<a href=\"mailto:\\1\">\\1</a>", $this->content);
|
||||
$this->content = preg_replace("#\[email\s*=\s*([\.\w\-]+\@[\.\w\-]+\.[\w\-]+)\s*\](.*?)\[\/email\]#i", "<a href=\"mailto:\\1\">\\2</a>", $this->content);
|
||||
|
||||
// URL tags ([url]someurl.tld[/url] and [url=someurl.tld]visit someurl.tld![/url])
|
||||
$this->content = preg_replace("#\[url\](\S+?)\[/url\]#ie", "\$this->parseURL('\\1')", $this->content);
|
||||
$this->content = preg_replace("#\[url\s*=\s*(\S+?)\s*\](.*?)\[\/url\]#ie", "\$this->parseURL('\\1', '\\2')", $this->content);
|
||||
$this->content = preg_replace("#\[url\s*=\s*\"\;\s*(\S+?)\s*\"\;\s*\](.*?)\[\/url\]#ie", "\$this->parseURL('\\1', '\\2')", $this->content);
|
||||
|
||||
// Img tags ([img]link/to/some/image.ext[/img])
|
||||
$this->content = preg_replace("#\[img\](.+?)\[/img\]#ie", "\$this->parseImg('\\1')", $this->content);
|
||||
|
||||
$this->content = str_replace("\n", "<br />", $this->content);
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a [code] tag
|
||||
*/
|
||||
private function parseCode($text, $type="text") {
|
||||
|
||||
// Wrap $text if required
|
||||
$text = wordwrap($text, 80);
|
||||
|
||||
// Add opening tags if required for PHP
|
||||
// Assumes that if there is no opening tag, that there is also no closing tag
|
||||
if($type == 'php') {
|
||||
if(strpos($text, '<?') === false) {
|
||||
$text = "<?php\n$text\n?>";
|
||||
}
|
||||
}
|
||||
|
||||
switch($type) {
|
||||
case "php":
|
||||
case "html":
|
||||
return "<p class=\"code\"><strong>".strtoupper($type).":</strong></p><div class=\"code $type\">".highlight_string(Convert::js2raw(Convert::xml2raw($text)), true)."</div>";
|
||||
break;
|
||||
case "text":
|
||||
default:
|
||||
return "<p class=\"code\"><strong>".strtoupper($type).":</strong></p><div class=\"code text\">".wordwrap($text)."</div>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a [url] tag
|
||||
*/
|
||||
private function parseURL($link, $text = "") {
|
||||
// If text isn't defined, make it the same as the link (for the [url][/url] tag)
|
||||
if(!$text) $text = $link;
|
||||
|
||||
// Remove ability to influence Javascript
|
||||
$link = preg_replace("/javascript:/i", "java script", $link);
|
||||
|
||||
// Ensure the URL starts with protocol://
|
||||
// If we don't, assume http://
|
||||
if (!preg_match("#^(\S+?)://#", $link)) {
|
||||
$link = "http://$link";
|
||||
}
|
||||
|
||||
// Rewrite *really* long URLs to beginning...end, but only where the URL is the same as the title
|
||||
// This will make the title 43 characters long if the link is >100 characters
|
||||
if(strlen($link) > 100 && $link == $text) {
|
||||
$text = substr($link, 0, 20)."...".substr($link, -20);
|
||||
}
|
||||
|
||||
$text = wordwrap($text, 78, " ", true);
|
||||
return "<a href=\"$link\" target=\"_blank\">$text</a>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a [img] tag
|
||||
*
|
||||
* TODO Set a maximum-images-per-post flag and check it
|
||||
*/
|
||||
private function parseImg($url) {
|
||||
// If we don't let them put this image tag in, just show the unchanged [img] tag so it's sorta obvious that it didn't work
|
||||
$noChange = "[img]".$url."[/img]";
|
||||
|
||||
// Disallow 'dynamic' images (dynamic being images with '?' in the url :P)
|
||||
// TODO Make this more robust, possibly allow it to be turned on/off via the CMS
|
||||
if(preg_match("/[?&;]/", $url) || preg_match("/javascript/i", $url)) return $noChange;
|
||||
|
||||
// Replace spaces with %20 and return the image tag
|
||||
return "<img src=\"".str_replace(" ", "%20", $url)."\" border=\"0\" alt=\"\">";
|
||||
}
|
||||
}
|
||||
<?php
|
||||
/**
|
||||
* TODO Investigate whether SSViewer will be fast enough to handle hundreds of little template files.
|
||||
*
|
||||
* A better (more SS) way of doing the HTML code in here is to place them all in small template files
|
||||
* (eg. BBCodeParser_Code.ss contains the HTML for BBCodeParser::parseCode()), but the overhead this
|
||||
* would cause is likely to make this very unusable, as nice as it would be.
|
||||
*/
|
||||
class BBCodeParser extends TextParser {
|
||||
static function usable_tags() {
|
||||
return new DataObjectSet(
|
||||
new ArrayData(array(
|
||||
"Title" => "Bold Text",
|
||||
"Example" => "[b]<b>Bold</b>[/b]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Italic Text",
|
||||
"Example" => "[i]<i>Italics</i>[/i]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Underlined Text",
|
||||
"Example" => "[u]<u>Underlined</u>[/u]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Struck-out Text",
|
||||
"Example" => "[s]<s>Struck-out</s>[/s]"
|
||||
)),
|
||||
|
||||
new ArrayData(array(
|
||||
"Title" => "Website link",
|
||||
"Description" => "Link to another website or URL",
|
||||
"Example" => "[url]http://www.website.com/[/url]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Website link",
|
||||
"Description" => "Link to another website or URL",
|
||||
"Example" => "[url=http://www.website.com/]Some website[/url]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Email link",
|
||||
"Description" => "Create link to an email address",
|
||||
"Example" => "[email]you@yoursite.com[/email]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "Email link",
|
||||
"Description" => "Create link to an email address",
|
||||
"Example" => "[email=you@yoursite.com]email me[/email]"
|
||||
)),
|
||||
|
||||
new ArrayData(array(
|
||||
"Title" => "Image",
|
||||
"Description" => "Show an image in your post",
|
||||
"Example" => "[img]http://www.website.com/image.jpg[/img]"
|
||||
)),
|
||||
|
||||
new ArrayData(array(
|
||||
"Title" => "Code Block",
|
||||
"Description" => "Unformatted code block",
|
||||
"Example" => "[code]Code block[/code]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "HTML Code Block",
|
||||
"Description" => "HTML-formatted code block",
|
||||
"Example" => "[html]HTML code block[/html]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "HTML Code Block",
|
||||
"Description" => "HTML-formatted code block",
|
||||
"Example" => "[code html]HTML code block[/code]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "PHP Code Block",
|
||||
"Description" => "PHP-formatted code block",
|
||||
"Example" => "[php]PHP code block[/php]"
|
||||
)),
|
||||
new ArrayData(array(
|
||||
"Title" => "PHP Code Block",
|
||||
"Description" => "PHP-formatted code block",
|
||||
"Example" => "[code php]PHP code block[/code]"
|
||||
))
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
function useable_tagsHTML(){
|
||||
$useabletags = "<ul class='bbcodeExamples'>";
|
||||
foreach($this->usable_tags()->toArray() as $tag){
|
||||
$useabletags = $useabletags."<li>".$tag->Example."</li>";
|
||||
}
|
||||
return $useabletags."</ul>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Meat of the class - finds tags to parse and offloads the parsing to member methods.
|
||||
*
|
||||
* parse() will look at various [square-bracketed] tags and replace them.
|
||||
* Complicated replacements are handled via private member methods.
|
||||
* Simple stuff is done inline inside the preg_replace to keep overhead low.
|
||||
*/
|
||||
function parse() {
|
||||
$this->content = str_replace(array('&', '<', '>'), array('&', '<', '>'), $this->content);
|
||||
|
||||
// Parse [code X] and base [code] tags
|
||||
$this->content = preg_replace("#\[code (.+?)\](.+?)\[/code\]#ies", "\$this->parseCode('\\2', '\\1')", $this->content);
|
||||
$this->content = preg_replace("#\[code\](.+?)\[/code\]#ies", "\$this->parseCode('\\1')", $this->content);
|
||||
|
||||
// Parse [html] and [php] tags (Shorthand [code html] and [code php] respectively)
|
||||
$this->content = preg_replace("#\[html\](.+?)\[/html\]#ies", "\$this->parseCode('\\1', 'html')", $this->content);
|
||||
$this->content = preg_replace("#\[php\](.+?)\[/php\]#ies", "\$this->parseCode('\\1', 'php')", $this->content);
|
||||
|
||||
// Simple HTML tags (bold, italic, underline, strike-through) - No need to call member methods for these
|
||||
$this->content = preg_replace("#\[b\](.+?)\[/b\]#is", "<strong>\\1</strong>", $this->content);
|
||||
$this->content = preg_replace("#\[i\](.+?)\[/i\]#is", "<em>\\1</em>", $this->content);
|
||||
$this->content = preg_replace("#\[u\](.+?)\[/u\]#is", "<u>\\1</u>", $this->content);
|
||||
$this->content = preg_replace("#\[s\](.+?)\[/s\]#is", "<s>\\1</s>", $this->content);
|
||||
|
||||
// Email tags ([email]name@domain.tld[/email] and [email=name@domain.tld]email me![/email])
|
||||
$this->content = preg_replace("#\[email\](\S+?)\[/email\]#i", "<a href=\"mailto:\\1\">\\1</a>", $this->content);
|
||||
$this->content = preg_replace("#\[email\s*=\s*([\.\w\-]+\@[\.\w\-]+\.[\w\-]+)\s*\](.*?)\[\/email\]#i", "<a href=\"mailto:\\1\">\\2</a>", $this->content);
|
||||
|
||||
// URL tags ([url]someurl.tld[/url] and [url=someurl.tld]visit someurl.tld![/url])
|
||||
$this->content = preg_replace("#\[url\](\S+?)\[/url\]#ie", "\$this->parseURL('\\1')", $this->content);
|
||||
$this->content = preg_replace("#\[url\s*=\s*(\S+?)\s*\](.*?)\[\/url\]#ie", "\$this->parseURL('\\1', '\\2')", $this->content);
|
||||
$this->content = preg_replace("#\[url\s*=\s*\"\;\s*(\S+?)\s*\"\;\s*\](.*?)\[\/url\]#ie", "\$this->parseURL('\\1', '\\2')", $this->content);
|
||||
|
||||
// Img tags ([img]link/to/some/image.ext[/img])
|
||||
$this->content = preg_replace("#\[img\](.+?)\[/img\]#ie", "\$this->parseImg('\\1')", $this->content);
|
||||
|
||||
$this->content = str_replace("\n", "<br />", $this->content);
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a [code] tag
|
||||
*/
|
||||
private function parseCode($text, $type="text") {
|
||||
|
||||
// Wrap $text if required
|
||||
$text = wordwrap($text, 80);
|
||||
|
||||
// Add opening tags if required for PHP
|
||||
// Assumes that if there is no opening tag, that there is also no closing tag
|
||||
if($type == 'php') {
|
||||
if(strpos($text, '<?') === false) {
|
||||
$text = "<?php\n$text\n?>";
|
||||
}
|
||||
}
|
||||
|
||||
switch($type) {
|
||||
case "php":
|
||||
case "html":
|
||||
return "<p class=\"code\"><strong>".strtoupper($type).":</strong></p><div class=\"code $type\">".highlight_string(Convert::js2raw(Convert::xml2raw($text)), true)."</div>";
|
||||
break;
|
||||
case "text":
|
||||
default:
|
||||
return "<p class=\"code\"><strong>".strtoupper($type).":</strong></p><div class=\"code text\">".wordwrap($text)."</div>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a [url] tag
|
||||
*/
|
||||
private function parseURL($link, $text = "") {
|
||||
// If text isn't defined, make it the same as the link (for the [url][/url] tag)
|
||||
if(!$text) $text = $link;
|
||||
|
||||
// Remove ability to influence Javascript
|
||||
$link = preg_replace("/javascript:/i", "java script", $link);
|
||||
|
||||
// Ensure the URL starts with protocol://
|
||||
// If we don't, assume http://
|
||||
if (!preg_match("#^(\S+?)://#", $link)) {
|
||||
$link = "http://$link";
|
||||
}
|
||||
|
||||
// Rewrite *really* long URLs to beginning...end, but only where the URL is the same as the title
|
||||
// This will make the title 43 characters long if the link is >100 characters
|
||||
if(strlen($link) > 100 && $link == $text) {
|
||||
$text = substr($link, 0, 20)."...".substr($link, -20);
|
||||
}
|
||||
|
||||
$text = wordwrap($text, 78, " ", true);
|
||||
return "<a href=\"$link\" target=\"_blank\">$text</a>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a [img] tag
|
||||
*
|
||||
* TODO Set a maximum-images-per-post flag and check it
|
||||
*/
|
||||
private function parseImg($url) {
|
||||
// If we don't let them put this image tag in, just show the unchanged [img] tag so it's sorta obvious that it didn't work
|
||||
$noChange = "[img]".$url."[/img]";
|
||||
|
||||
// Disallow 'dynamic' images (dynamic being images with '?' in the url :P)
|
||||
// TODO Make this more robust, possibly allow it to be turned on/off via the CMS
|
||||
if(preg_match("/[?&;]/", $url) || preg_match("/javascript/i", $url)) return $noChange;
|
||||
|
||||
// Replace spaces with %20 and return the image tag
|
||||
return "<img src=\"".str_replace(" ", "%20", $url)."\" border=\"0\" alt=\"\">";
|
||||
}
|
||||
}
|
||||
?>
|
@ -1,51 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Parses text in a variety of ways.
|
||||
*
|
||||
* Called from a template by $Content.Parse(SubClassName), similar to $Content.XML.
|
||||
* This will work on any Text database field (Or a sub-class, such as HTMLText,
|
||||
* although it's usefulness in this situation is more limited).
|
||||
*
|
||||
* Any sub-classes of TextParser must implement a parse() method.
|
||||
* This should take $this->content and parse it however you want. For an example
|
||||
* of the implementation, @see BBCodeParser.
|
||||
*
|
||||
* Your sub-class will be initialized with a string of text, then parse() will be called.
|
||||
* parse() should (after processing) return the formatted string.
|
||||
*
|
||||
* Note: $this->content will have NO conversions applied to it.
|
||||
* You should run Covert::raw2xml or whatever is appropriate before using it.
|
||||
*
|
||||
* Optionally (but recommended), is creating a static usable_tags method,
|
||||
* which will return a DataObjectSet of all the usable tags that can be parsed.
|
||||
* This will (mostly) be used to create helper blocks - telling users what things will be parsed.
|
||||
* Again, @see BBCodeParser for an example of the syntax
|
||||
*
|
||||
* TODO Define a proper syntax for (or refactor) usable_tags that can be extended as needed.
|
||||
*/
|
||||
abstract class TextParser extends Object {
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* Creates a new TextParser object.
|
||||
*
|
||||
* @param string $content The contents of the dbfield
|
||||
*/
|
||||
function __construct($content = "") {
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, shouldn't really be used, but it's here if you want it
|
||||
*/
|
||||
function setContent($content = "") {
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define your own parse method to parse $this->content appropriately.
|
||||
* See the class doc-block for more implementation details.
|
||||
*/
|
||||
abstract function parse();
|
||||
}
|
||||
<?php
|
||||
/**
|
||||
* Parses text in a variety of ways.
|
||||
*
|
||||
* Called from a template by $Content.Parse(SubClassName), similar to $Content.XML.
|
||||
* This will work on any Text database field (Or a sub-class, such as HTMLText,
|
||||
* although it's usefulness in this situation is more limited).
|
||||
*
|
||||
* Any sub-classes of TextParser must implement a parse() method.
|
||||
* This should take $this->content and parse it however you want. For an example
|
||||
* of the implementation, @see BBCodeParser.
|
||||
*
|
||||
* Your sub-class will be initialized with a string of text, then parse() will be called.
|
||||
* parse() should (after processing) return the formatted string.
|
||||
*
|
||||
* Note: $this->content will have NO conversions applied to it.
|
||||
* You should run Covert::raw2xml or whatever is appropriate before using it.
|
||||
*
|
||||
* Optionally (but recommended), is creating a static usable_tags method,
|
||||
* which will return a DataObjectSet of all the usable tags that can be parsed.
|
||||
* This will (mostly) be used to create helper blocks - telling users what things will be parsed.
|
||||
* Again, @see BBCodeParser for an example of the syntax
|
||||
*
|
||||
* TODO Define a proper syntax for (or refactor) usable_tags that can be extended as needed.
|
||||
*/
|
||||
abstract class TextParser extends Object {
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* Creates a new TextParser object.
|
||||
*
|
||||
* @param string $content The contents of the dbfield
|
||||
*/
|
||||
function __construct($content = "") {
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method, shouldn't really be used, but it's here if you want it
|
||||
*/
|
||||
function setContent($content = "") {
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define your own parse method to parse $this->content appropriately.
|
||||
* See the class doc-block for more implementation details.
|
||||
*/
|
||||
abstract function parse();
|
||||
}
|
||||
?>
|
Loading…
Reference in New Issue
Block a user