$n->delete(DB_DATAOBJECT_WHEREADD_ONLY);
}
- function toRooSingleArray($authUser, $request)
- {
- $ret = $this->toArray();
-
- if(!empty($ret['keyword_filters'])){
- $keywords = array_unique(array_filter(explode(',', $ret['keyword_filters'])));
-
- $clipping_keywords = DB_DataObject::factory('clipping_keywords');
- $clipping_keywords->whereAddIn('id', $keywords, 'int');
-
- $li = array();
-
- foreach ($clipping_keywords->fetchAll('id', 'keyword') as $k => $v){
- $li[] = array(
- 'id' => $k,
- 'keyword' => $v
- );
- }
-
- $ret['keywords'] = json_encode($li);
- }
-
- return $ret;
- }
-
- function toRooArray($request)
- {
- $ret = $this->toArray();
-
- if(!empty($ret['keyword_filters'])){
- $keywords = array_unique(array_filter(explode(',', $ret['keyword_filters'])));
-
- $clipping_keywords = DB_DataObject::factory('clipping_keywords');
- $clipping_keywords->whereAddIn('id', $keywords, 'int');
-
- $ret['keywords'] = implode(',', $clipping_keywords->fetchAll('keyword'));
- }
-
- return $ret;
- }
}
person_id = Person:id
last_event_id = Events:id
method_id = core_enum:id
-campaign_id = Projects:id
"157e432ec303efd7d537b653cb255ccc" : "on day(s)",
"236df51bb0e6416236e255b528346fca" : "Timezone",
"44c68bed631ff6e62aecc4a4d32176e6" : "Select timezone",
- "867343577fa1f33caa632a19543bd252" : "Keywords",
"1243daf593fa297e07ab03bf06d925af" : "Searching...",
"b26686c0a708faee42861d8b905e882e" : "Last Sent",
"c1d32776cd2d2afcd2c45a52f58679f4" : "Modify Recurrent Notifications",
"show" : "function (_self)\n{\n _this.grid.ds.load({});\n}"
},
"modal" : true,
- "title" : "Modify Recurrent Notifications",
"xtype" : "LayoutDialog",
+ "title" : "Modify Recurrent Notifications",
"width" : 800,
"$ xns" : "Roo",
"resizable" : true,
"listeners" : {
"|activate" : "function() {\n _this.panel = this;\n if (_this.grid) {\n// _this.grid.footer.onClick('first');\n }\n}"
},
+ "region" : "center",
"fitToFrame" : true,
"background" : false,
- "region" : "center",
"title" : "core_notify_recur",
"xtype" : "GridPanel",
"fitContainer" : true,
"items" : [
{
"listeners" : {
- "cellclick" : "function (_self, rowIndex, columnIndex, e)\n{\n var di = this.colModel.getDataIndex(columnIndex);\n if (di != 'keyword_filters') {\n return;\n }\n \n var d = this.ds.getAt(rowIndex);\n \n if(!d || d.data.id * 1 < 1){\n return;\n }\n \n Pman.Dialog.CoreNotifyRecurKeywords.show({id : d.data.id}, function(res){\n _this.grid.ds.load({});\n });\n \n}",
"|render" : "function() \n{\n _this.grid = this; \n //_this.dialog = Pman.Dialog.FILL_IN\n if (_this.panel.active) {\n // this.footer.onClick('first');\n }\n}",
"afteredit" : "function (e)\n{\n e.record.commit();\n}"
},
- "autoExpandColumn" : "keyword_filters",
+ "autoExpandColumn" : "freq_day",
"xtype" : "EditorGrid",
"loadMask" : true,
"clicksToEdit" : 1,
"items" : [
{
"$ url" : "baseURL + '/Roo/core_notify_recur.php'",
- "method" : "GET",
"xtype" : "HttpProxy",
+ "method" : "GET",
"$ xns" : "Roo.data",
"* prop" : "proxy"
},
},
{
"xtype" : "ColumnModel",
- "header" : "Type",
"width" : 120,
+ "header" : "Type",
"$ renderer" : "function(v,x,r) {\n return String.format('{0}', r.data.method_id_display_name); \n}",
"$ xns" : "Roo.grid",
"* prop" : "colModel[]",
"items" : [
{
"$ url" : "baseURL + '/Roo/core_enum.php'",
- "method" : "GET",
"xtype" : "HttpProxy",
+ "method" : "GET",
"$ xns" : "Roo.data",
"* prop" : "proxy"
},
},
{
"xtype" : "ColumnModel",
- "header" : "From",
"width" : 75,
+ "header" : "From",
"$ renderer" : "function(v) { return String.format('{0}', v ? v.format('d/M/Y') : ''); }",
"$ xns" : "Roo.grid",
"* prop" : "colModel[]",
},
{
"xtype" : "ColumnModel",
- "header" : "Until",
"width" : 75,
+ "header" : "Until",
"$ renderer" : "function(v) { return String.format('{0}', v ? v.format('d/M/Y') : ''); }",
"$ xns" : "Roo.grid",
"* prop" : "colModel[]",
},
{
"xtype" : "ColumnModel",
- "header" : "on day(s)",
"width" : 150,
+ "header" : "on day(s)",
"$ renderer" : "function(v,x,r) { \n \n if (v.length) {\n \n var cm = _this.grid.colModel;\n \n var ci = cm.getColumnByDataIndex(this.name);\n \n var tv = [];\n var vals = Roo.decode(v);\n Roo.each(vals, function(k) {\n var r = this.findRecord(this.valueField, k);\n if(r){\n tv.push(r.data[this.displayField]);\n }else if(this.valueNotFoundText !== undefined){\n tv.push( this.valueNotFoundText );\n }\n },ci.editor.field);\n\n r.data[this.name + '_name'] = tv.join(', ');\n return String.format('{0}',tv.join(', '));\n\n \n \n }\n r.data[this.name + '_name'] = '';\n return String.format('{0}', r.data.freq_day_name || v); \n \n}",
"$ xns" : "Roo.grid",
"* prop" : "colModel[]",
},
{
"xtype" : "ColumnModel",
- "header" : "at Hour(s)",
"width" : 100,
+ "header" : "at Hour(s)",
"$ renderer" : "function(v,x,r) { \n \n \n if (v.length) {\n \n var cm = _this.grid.colModel;\n \n var ci = cm.getColumnByDataIndex(this.name);\n \n var tv = [];\n var vals = Roo.decode(v);\n Roo.each(vals, function(k) {\n var r = this.findRecord(this.valueField, k);\n if(r){\n tv.push(r.data[this.displayField]);\n }else if(this.valueNotFoundText !== undefined){\n tv.push( this.valueNotFoundText );\n }\n },ci.editor.field);\n\n r.data[this.name + '_name'] = tv.join(', ');\n return String.format('{0}',tv.join(', '));\n\n \n \n }\n r.data[this.name + '_name'] = '';\n return String.format('{0}', r.data.freq_hour_name || v); \n \n}",
"$ xns" : "Roo.grid",
"* prop" : "colModel[]",
},
{
"xtype" : "ColumnModel",
- "header" : "Timezone",
"width" : 100,
+ "header" : "Timezone",
"$ renderer" : "function(v) { return String.format('{0}', v); }",
"$ xns" : "Roo.grid",
"* prop" : "colModel[]",
"items" : [
{
"$ url" : "baseURL + '/Core/I18n/Timezone.php'\n",
- "xtype" : "HttpProxy",
"method" : "GET",
+ "xtype" : "HttpProxy",
"$ xns" : "Roo.data",
"* prop" : "proxy"
},
},
{
"xtype" : "ColumnModel",
- "width" : 75,
"header" : "Last Sent",
+ "width" : 75,
"$ renderer" : "function(v) { return String.format('{0}', v ? v : 'never'); }",
"$ xns" : "Roo.grid",
"* prop" : "colModel[]",
"dataIndex" : "last_event_id"
- },
- {
- "xtype" : "ColumnModel",
- "header" : "Keywords",
- "width" : 75,
- "$ renderer" : "function(v,x,r) { \n return String.format('{0}', v ? r.data.keywords : ''); \n}",
- "$ xns" : "Roo.grid",
- "* prop" : "colModel[]",
- "dataIndex" : "keyword_filters"
}
]
}
'157e432ec303efd7d537b653cb255ccc' :"on day(s)",
'236df51bb0e6416236e255b528346fca' :"Timezone",
'44c68bed631ff6e62aecc4a4d32176e6' :"Select timezone",
- '867343577fa1f33caa632a19543bd252' :"Keywords",
'1243daf593fa297e07ab03bf06d925af' :"Searching...",
'b26686c0a708faee42861d8b905e882e' :"Last Sent",
'c1d32776cd2d2afcd2c45a52f58679f4' :"Modify Recurrent Notifications",
},
'|xns' : 'Roo.grid',
- autoExpandColumn : 'keyword_filters',
+ autoExpandColumn : 'freq_day',
clicksToEdit : 1,
loadMask : true,
xns : Roo.grid,
width : 75,
xns : Roo.grid,
xtype : 'ColumnModel'
- },
-{
- '|xns' : 'Roo.grid',
- dataIndex : 'keyword_filters',
- header : _this._strings['867343577fa1f33caa632a19543bd252'],
- renderer : function(v,x,r) {
- return String.format('{0}', v ? r.data.keywords : '');
- },
- width : 75,
- xns : Roo.grid,
- xtype : 'ColumnModel'
}
],
listeners : {
{
e.record.commit();
},
- cellclick : function (_self, rowIndex, columnIndex, e)
- {
- var di = this.colModel.getDataIndex(columnIndex);
- if (di != 'keyword_filters') {
- return;
- }
-
- var d = this.ds.getAt(rowIndex);
-
- if(!d || d.data.id * 1 < 1){
- return;
- }
-
- Pman.Dialog.CoreNotifyRecurKeywords.show({id : d.data.id}, function(res){
- _this.grid.ds.load({});
- });
-
- },
render : function()
{
_this.grid = this;
+++ /dev/null
-{
- "name" : "Pman.Dialog.CoreNotifyRecurKeywords",
- "parent" : "",
- "title" : "",
- "path" : "/home/edward/gitlive/Pman.Core/Pman.Dialog.CoreNotifyRecurKeywords.bjs",
- "permname" : "",
- "modOrder" : "001",
- "strings" : {
- "0ee0f676f631ad4e8a5844314a3a20de" : "Select campaign",
- "1243daf593fa297e07ab03bf06d925af" : "Searching...",
- "ea4788705e6873b424c65e91c2846b19" : "Cancel",
- "e0aa021e21dddbd6d8cecec71e9cf564" : "OK",
- "790f855c2139f2faecb810519e90b833" : "Add Notification Keywords",
- "ded4cba1b04eb8236e24a3e39470d8a7" : "Select Campaign"
- },
- "items" : [
- {
- "listeners" : {
- "show" : "function (_self)\n{\n \n}"
- },
- "modal" : true,
- "collapsible" : false,
- "background" : true,
- "title" : "Add Notification Keywords",
- "xtype" : "LayoutDialog",
- "width" : 600,
- "$ xns" : "Roo",
- "closable" : false,
- "resizable" : false,
- "height" : 180,
- "items" : [
- {
- "xtype" : "LayoutRegion",
- "$ xns" : "Roo",
- "titlebar" : false,
- "* prop" : "center"
- },
- {
- "region" : "center",
- "fitToFrame" : true,
- "background" : true,
- "xtype" : "ContentPanel",
- "$ xns" : "Roo",
- "items" : [
- {
- "listeners" : {
- "|actioncomplete" : "function (_self, action)\n{\n if (action.type == 'setdata') {\n \n if(_this.data.id){\n _this.dialog.el.mask(\"Loading\");\n this.load({ method: 'GET', params: { '_id' : _this.data.id }}); \n }\n \n return;\n }\n if (action.type == 'load') {\n \n _this.dialog.el.unmask();\n \n _this.data = action.result.data;\n \n if(typeof(_this.data.keywords) != 'undefined'){\n var n = Roo.decode(_this.data.keywords);\n _this.form.findField('keyword_filters').setValue(n);\n }\n \n return;\n }\n if (action.type == 'submit' ) {\n _this.dialog.el.unmask();\n _this.dialog.hide();\n\n if (_this.callback) {\n _this.callback.call(_this, action.result.data);\n }\n _this.form.reset();\n }\n}\n",
- "|rendered" : "function (form)\n{\n _this.form = form;\n}"
- },
- "$ url" : "baseURL + '/Roo/Core_notify_recur.php'",
- "xtype" : "Form",
- "method" : "POST",
- "style" : "margin: 5px",
- "$ xns" : "Roo.form",
- "items" : [
- {
- "alwaysQuery" : true,
- "listWidth" : 400,
- "triggerAction" : "all",
- "fieldLabel" : "Campaign",
- "forceSelection" : true,
- "selectOnFocus" : true,
- "pageSize" : 20,
- "Number width" : 400,
- "displayField" : "name",
- "emptyText" : "Select Campaign",
- "hiddenName" : "campaign_id",
- "minChars" : 2,
- "valueField" : "id",
- "xtype" : "ComboBox",
- "allowBlank" : false,
- "typeAhead" : false,
- "editable" : true,
- "$ xns" : "Roo.form",
- "name" : "campaign_id_name",
- "qtip" : "Select campaign",
- "queryParam" : "query[name]",
- "tpl" : "<div class=\"x-grid-cell-text x-btn button\"><b>{name}</b></div>",
- "loadingText" : "Searching...",
- "items" : [
- {
- "listeners" : {
- "|beforeload" : "function (_self, o){\n o.params = o.params || {};\n \n}\n"
- },
- "xtype" : "Store",
- "remoteSort" : true,
- "$ sortInfo" : "{ direction : 'DESC', field: 'id' }",
- "$ xns" : "Roo.data",
- "* prop" : "store",
- "items" : [
- {
- "$ url" : "baseURL + '/Roo/Projects.php'",
- "method" : "GET",
- "xtype" : "HttpProxy",
- "$ xns" : "Roo.data",
- "* prop" : "proxy"
- },
- {
- "id" : "id",
- "root" : "data",
- "xtype" : "JsonReader",
- "$ xns" : "Roo.data",
- "$ fields" : "[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"name\",\"type\":\"string\"}]",
- "* prop" : "reader",
- "totalProperty" : "total"
- }
- ]
- }
- ]
- },
- {
- "Boolean allowBlank" : false,
- "fieldLabel" : "Keywords",
- "hiddenName" : "keyword_filters",
- "xtype" : "ComboBoxArray",
- "width" : 410,
- "$ xns" : "Roo.form",
- "name" : "keyword_filters_name",
- "items" : [
- {
- "alwaysQuery" : true,
- "listWidth" : 400,
- "triggerAction" : "all",
- "fieldLabel" : "Keyword",
- "forceSelection" : true,
- "displayField" : "keyword",
- "minChars" : 2,
- "valueField" : "id",
- "xtype" : "ComboBox",
- "allowBlank" : true,
- "editable" : true,
- "width" : 400,
- "$ xns" : "Roo.form",
- "* prop" : "combo",
- "queryParam" : "query[keyword]",
- "tpl" : "<div class=\"x-grid-cell-text x-btn button\"><b>{keyword}</b> </div>",
- "items" : [
- {
- "listeners" : {
- "beforeload" : "function (_self, o){\n o.params = o.params || {};\n \n var s = _this.form.findField('campaign_id').getValue() * 1;\n \n if(isNaN(s) || s < 1){\n return false;\n }\n \n o.params.is_active = 1;\n o.params.is_keyword = 1;\n o.params.project_id = s;\n}\n"
- },
- "xtype" : "Store",
- "remoteSort" : true,
- "$ sortInfo" : "{ direction : 'ASC', field: 'display_name' }",
- "$ xns" : "Roo.data",
- "* prop" : "store",
- "items" : [
- {
- "$ url" : "baseURL + '/Roo/clipping_keywords.php'",
- "xtype" : "HttpProxy",
- "method" : "GET",
- "$ xns" : "Roo.data",
- "* prop" : "proxy"
- },
- {
- "id" : "code",
- "root" : "data",
- "xtype" : "JsonReader",
- "$ fields" : "[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"keyword\",\"type\":\"string\"}]",
- "$ xns" : "Roo.data",
- "* prop" : "reader",
- "totalProperty" : "total"
- }
- ]
- }
- ]
- }
- ]
- },
- {
- "xtype" : "Hidden",
- "$ xns" : "Roo.form",
- "name" : "id"
- }
- ]
- }
- ]
- },
- {
- "listeners" : {
- "|click" : "function() {\n _this.form.reset();\n _this.dialog.hide();\n}"
- },
- "text" : "Cancel",
- "xtype" : "Button",
- "$ xns" : "Roo",
- "* prop" : "buttons[]"
- },
- {
- "listeners" : {
- "|click" : "function() {\n \n _this.form.doAction('submit');\n \n}"
- },
- "text" : "OK",
- "xtype" : "Button",
- "$ xns" : "Roo",
- "* prop" : "buttons[]"
- }
- ]
- }
- ]
-}
\ No newline at end of file
+++ /dev/null
-//<script type="text/javascript">
-
-// Auto generated file - created by app.Builder.js- do not edit directly (at present!)
-
-Roo.namespace('Pman.Dialog');
-
-Pman.Dialog.CoreNotifyRecurKeywords = {
-
- _strings : {
- '0ee0f676f631ad4e8a5844314a3a20de' :"Select campaign",
- '1243daf593fa297e07ab03bf06d925af' :"Searching...",
- 'ea4788705e6873b424c65e91c2846b19' :"Cancel",
- 'e0aa021e21dddbd6d8cecec71e9cf564' :"OK",
- '790f855c2139f2faecb810519e90b833' :"Add Notification Keywords",
- 'ded4cba1b04eb8236e24a3e39470d8a7' :"Select Campaign"
- },
-
- dialog : false,
- callback: false,
-
- show : function(data, cb)
- {
- if (!this.dialog) {
- this.create();
- }
-
- this.callback = cb;
- this.data = data;
- this.dialog.show(this.data._el);
- if (this.form) {
- this.form.reset();
- this.form.setValues(data);
- this.form.fireEvent('actioncomplete', this.form, { type: 'setdata', data: data });
- }
-
- },
-
- create : function()
- {
- var _this = this;
- this.dialog = Roo.factory({
- center : {
- '|xns' : 'Roo',
- titlebar : false,
- xns : Roo,
- xtype : 'LayoutRegion'
- },
- '|xns' : 'Roo',
- background : true,
- closable : false,
- collapsible : false,
- height : 180,
- modal : true,
- resizable : false,
- title : _this._strings['790f855c2139f2faecb810519e90b833'],
- width : 600,
- xns : Roo,
- xtype : 'LayoutDialog',
- buttons : [
- {
- '|xns' : 'Roo',
- text : _this._strings['ea4788705e6873b424c65e91c2846b19'],
- xns : Roo,
- xtype : 'Button',
- listeners : {
- click : function() {
- _this.form.reset();
- _this.dialog.hide();
- }
- }
- },
-{
- '|xns' : 'Roo',
- text : _this._strings['e0aa021e21dddbd6d8cecec71e9cf564'],
- xns : Roo,
- xtype : 'Button',
- listeners : {
- click : function() {
-
- _this.form.doAction('submit');
-
- }
- }
- }
- ],
- listeners : {
- show : function (_self)
- {
-
- }
- },
- items : [
- {
- '|xns' : 'Roo',
- background : true,
- fitToFrame : true,
- region : 'center',
- xns : Roo,
- xtype : 'ContentPanel',
- items : [
- {
- '|xns' : 'Roo.form',
- method : 'POST',
- style : 'margin: 5px',
- url : baseURL + '/Roo/Core_notify_recur.php',
- xns : Roo.form,
- xtype : 'Form',
- listeners : {
- actioncomplete : function (_self, action)
- {
- if (action.type == 'setdata') {
-
- if(_this.data.id){
- _this.dialog.el.mask("Loading");
- this.load({ method: 'GET', params: { '_id' : _this.data.id }});
- }
-
- return;
- }
- if (action.type == 'load') {
-
- _this.dialog.el.unmask();
-
- _this.data = action.result.data;
-
- if(typeof(_this.data.keywords) != 'undefined'){
- var n = Roo.decode(_this.data.keywords);
- _this.form.findField('keyword_filters').setValue(n);
- }
-
- return;
- }
- if (action.type == 'submit' ) {
- _this.dialog.el.unmask();
- _this.dialog.hide();
-
- if (_this.callback) {
- _this.callback.call(_this, action.result.data);
- }
- _this.form.reset();
- }
- },
- rendered : function (form)
- {
- _this.form = form;
- }
- },
- items : [
- {
- store : {
- proxy : {
- '|xns' : 'Roo.data',
- method : 'GET',
- url : baseURL + '/Roo/Projects.php',
- xns : Roo.data,
- xtype : 'HttpProxy'
- },
- reader : {
- '|xns' : 'Roo.data',
- fields : [{"name":"id","type":"int"},{"name":"name","type":"string"}],
- id : 'id',
- root : 'data',
- totalProperty : 'total',
- xns : Roo.data,
- xtype : 'JsonReader'
- },
- '|xns' : 'Roo.data',
- remoteSort : true,
- sortInfo : { direction : 'DESC', field: 'id' },
- xns : Roo.data,
- xtype : 'Store',
- listeners : {
- beforeload : function (_self, o){
- o.params = o.params || {};
-
- }
- },
- items : [
-
- ]
-
- },
- '|xns' : 'Roo.form',
- allowBlank : false,
- alwaysQuery : true,
- displayField : 'name',
- editable : true,
- emptyText : _this._strings['ded4cba1b04eb8236e24a3e39470d8a7'],
- fieldLabel : 'Campaign',
- forceSelection : true,
- hiddenName : 'campaign_id',
- listWidth : 400,
- loadingText : _this._strings['1243daf593fa297e07ab03bf06d925af'],
- minChars : 2,
- name : 'campaign_id_name',
- pageSize : 20,
- qtip : _this._strings['0ee0f676f631ad4e8a5844314a3a20de'],
- queryParam : 'query[name]',
- selectOnFocus : true,
- tpl : '<div class=\"x-grid-cell-text x-btn button\"><b>{name}</b></div>',
- triggerAction : 'all',
- typeAhead : false,
- valueField : 'id',
- width : 400,
- xns : Roo.form,
- xtype : 'ComboBox',
- items : [
-
- ]
-
- },
- {
- combo : {
- store : {
- proxy : {
- '|xns' : 'Roo.data',
- method : 'GET',
- url : baseURL + '/Roo/clipping_keywords.php',
- xns : Roo.data,
- xtype : 'HttpProxy'
- },
- reader : {
- '|xns' : 'Roo.data',
- fields : [{"name":"id","type":"int"},{"name":"keyword","type":"string"}],
- id : 'code',
- root : 'data',
- totalProperty : 'total',
- xns : Roo.data,
- xtype : 'JsonReader'
- },
- '|xns' : 'Roo.data',
- remoteSort : true,
- sortInfo : { direction : 'ASC', field: 'display_name' },
- xns : Roo.data,
- xtype : 'Store',
- listeners : {
- beforeload : function (_self, o){
- o.params = o.params || {};
-
- var s = _this.form.findField('campaign_id').getValue() * 1;
-
- if(isNaN(s) || s < 1){
- return false;
- }
-
- o.params.is_active = 1;
- o.params.is_keyword = 1;
- o.params.project_id = s;
- }
- },
- items : [
-
- ]
-
- },
- '|xns' : 'Roo.form',
- allowBlank : true,
- alwaysQuery : true,
- displayField : 'keyword',
- editable : true,
- fieldLabel : 'Keyword',
- forceSelection : true,
- listWidth : 400,
- minChars : 2,
- queryParam : 'query[keyword]',
- tpl : '<div class=\"x-grid-cell-text x-btn button\"><b>{keyword}</b> </div>',
- triggerAction : 'all',
- valueField : 'id',
- width : 400,
- xns : Roo.form,
- xtype : 'ComboBox',
- items : [
-
- ]
-
- },
- '|xns' : 'Roo.form',
- allowBlank : false,
- fieldLabel : 'Keywords',
- hiddenName : 'keyword_filters',
- name : 'keyword_filters_name',
- width : 410,
- xns : Roo.form,
- xtype : 'ComboBoxArray',
- items : [
-
- ]
-
- },
- {
- '|xns' : 'Roo.form',
- name : 'id',
- xns : Roo.form,
- xtype : 'Hidden'
- }
- ]
-
- }
- ]
-
- }
- ]
-
- });
- }
-};
+++ /dev/null
-
-ALTER TABLE core_notify_recur ADD COLUMN campaign_id INT(11) NOT NULL DEFAULT 0;
-
-ALTER TABLE core_notify_recur ADD COLUMN keyword_filters TEXT NOT NULL DEFAULT '';
\ No newline at end of file