diff --git a/core/model/fieldtypes/SSDatetime.php b/core/model/fieldtypes/SSDatetime.php index ec26eb536..0bc6529d6 100644 --- a/core/model/fieldtypes/SSDatetime.php +++ b/core/model/fieldtypes/SSDatetime.php @@ -1,34 +1,34 @@ -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 ); - } -} - -?> +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 ); + } +} + +?> diff --git a/filesystem/PostBackup.php b/filesystem/PostBackup.php index 4a1f31fce..11e981aca 100644 --- a/filesystem/PostBackup.php +++ b/filesystem/PostBackup.php @@ -1,39 +1,39 @@ -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 = <<class} - -$postData -***END ENTRY*** -BAK; - - fwrite($backupFile, $backup); - fclose($backupFile); - } - -} -?> +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 = <<class} + +$postData +***END ENTRY*** +BAK; + + fwrite($backupFile, $backup); + fclose($backupFile); + } + +} +?> diff --git a/forms/TableField.php b/forms/TableField.php index caee6e6ae..a87eaea27 100644 --- a/forms/TableField.php +++ b/forms/TableField.php @@ -1,698 +1,698 @@ - 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$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 ("
  • Type: ".$fieldType." fieldName: ". $filedName. " Title: ".$fieldTitle."
  • "); - $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"; - } - -} - + 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$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 ("
  • Type: ".$fieldType." fieldName: ". $filedName. " Title: ".$fieldTitle."
  • "); + $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"; + } + +} + ?> \ No newline at end of file diff --git a/javascript/RedirectorPage.js b/javascript/RedirectorPage.js index 61f27296e..304e4596e 100644 --- a/javascript/RedirectorPage.js +++ b/javascript/RedirectorPage.js @@ -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; + } + } +}); diff --git a/parsers/BBCodeParser.php b/parsers/BBCodeParser.php index cf8b3452a..2cef61dae 100644 --- a/parsers/BBCodeParser.php +++ b/parsers/BBCodeParser.php @@ -1,204 +1,204 @@ - "Bold Text", - "Example" => "[b]Bold[/b]" - )), - new ArrayData(array( - "Title" => "Italic Text", - "Example" => "[i]Italics[/i]" - )), - new ArrayData(array( - "Title" => "Underlined Text", - "Example" => "[u]Underlined[/u]" - )), - new ArrayData(array( - "Title" => "Struck-out Text", - "Example" => "[s]Struck-out[/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 = "
      "; - foreach($this->usable_tags()->toArray() as $tag){ - $useabletags = $useabletags."
    • ".$tag->Example."
    • "; - } - return $useabletags."
    "; - } - - /** - * 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", "\\1", $this->content); - $this->content = preg_replace("#\[i\](.+?)\[/i\]#is", "\\1", $this->content); - $this->content = preg_replace("#\[u\](.+?)\[/u\]#is", "\\1", $this->content); - $this->content = preg_replace("#\[s\](.+?)\[/s\]#is", "\\1", $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", "\\1", $this->content); - $this->content = preg_replace("#\[email\s*=\s*([\.\w\-]+\@[\.\w\-]+\.[\w\-]+)\s*\](.*?)\[\/email\]#i", "\\2", $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", "
    ", $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 "

    ".strtoupper($type).":

    ".highlight_string(Convert::js2raw(Convert::xml2raw($text)), true)."
    "; - break; - case "text": - default: - return "

    ".strtoupper($type).":

    ".wordwrap($text)."
    "; - 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 "$text"; - } - - /** - * 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 "\"\""; - } -} + "Bold Text", + "Example" => "[b]Bold[/b]" + )), + new ArrayData(array( + "Title" => "Italic Text", + "Example" => "[i]Italics[/i]" + )), + new ArrayData(array( + "Title" => "Underlined Text", + "Example" => "[u]Underlined[/u]" + )), + new ArrayData(array( + "Title" => "Struck-out Text", + "Example" => "[s]Struck-out[/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 = "
      "; + foreach($this->usable_tags()->toArray() as $tag){ + $useabletags = $useabletags."
    • ".$tag->Example."
    • "; + } + return $useabletags."
    "; + } + + /** + * 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", "\\1", $this->content); + $this->content = preg_replace("#\[i\](.+?)\[/i\]#is", "\\1", $this->content); + $this->content = preg_replace("#\[u\](.+?)\[/u\]#is", "\\1", $this->content); + $this->content = preg_replace("#\[s\](.+?)\[/s\]#is", "\\1", $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", "\\1", $this->content); + $this->content = preg_replace("#\[email\s*=\s*([\.\w\-]+\@[\.\w\-]+\.[\w\-]+)\s*\](.*?)\[\/email\]#i", "\\2", $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", "
    ", $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 "

    ".strtoupper($type).":

    ".highlight_string(Convert::js2raw(Convert::xml2raw($text)), true)."
    "; + break; + case "text": + default: + return "

    ".strtoupper($type).":

    ".wordwrap($text)."
    "; + 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 "$text"; + } + + /** + * 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 "\"\""; + } +} ?> \ No newline at end of file diff --git a/parsers/TextParser.php b/parsers/TextParser.php index f18ee002c..460d43e2a 100644 --- a/parsers/TextParser.php +++ b/parsers/TextParser.php @@ -1,51 +1,51 @@ -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(); -} +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(); +} ?> \ No newline at end of file