5989b17c by Joshua Tundag

add examples

1 parent 48468437
...@@ -112,4 +112,6 @@ if (!function_exists('flag')){ ...@@ -112,4 +112,6 @@ if (!function_exists('flag')){
112 flag('mode'); 112 flag('mode');
113 flag('source'); 113 flag('source');
114 flag('pmt', 'rec'); 114 flag('pmt', 'rec');
115 ```
...\ No newline at end of file ...\ No newline at end of file
115 ```
116
117 [See example for more snippets.](/examples)
...\ No newline at end of file ...\ No newline at end of file
......
1 <?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
2
3 class Api extends MY_Controller{
4 public function __construct() {
5 parent::__construct();
6 $this->authenticate_admin_user(false);
7 $admin_data = admin_data();
8 if(!$this->authenticate_administrator_role($admin_data)){
9 /*** Guest redirect ***/
10 $this->admin_user_guest($admin_data);
11 /*** Support redirect ***/
12 $this->admin_user_support($admin_data);
13 }
14 $this->load->library('form_validation');
15 }
16
17 public function get_flags_for_table(){
18 $tableOptions = $this->input->get();
19 $flagManager = new \BTFlags\FlagManager();
20
21 $table = array();
22 $tableOptions['sortBy'] = 'DESC';
23 if($tableOptions['sortOrder'] && strpos($tableOptions['sortOrder'], '+') === 0){
24 $tableOptions['sortOrder'] = substr($tableOptions['sortOrder'], 1);
25 $tableOptions['sortBy'] = 'ASC';
26 }
27 $tableOptions['filter'] = isset($tableOptions['filter']) ? $tableOptions['filter'] : '';
28 $tableOptions['mode'] = isset($_COOKIE['mode']) ? $_COOKIE['mode'] : 'live';
29 $table['data'] = $flagManager->getAllForTable($tableOptions);
30 $scope = $this;
31
32 $table['total'] = $flagManager->getAllCountForTable($tableOptions);
33 $table['per_page'] = (int)$tableOptions['perPage'];
34 $table['current_page'] = (int)$tableOptions['page'];
35 $table['sort'] = $tableOptions['sortOrder'];
36 $table['last_page'] = ceil($table['total'] / $table['per_page']);
37 $table['next_page_url'] = 'api/get_permissions_for_table?page=' . ($tableOptions['page'] + 1);
38 $table['prev_page_url'] = null;
39 $table['from'] = (((int)$tableOptions['page'] - 1) * $table['per_page']) + 1;
40 $table['to'] = ((((int)$tableOptions['page']) * $table['per_page']) < $table['total']) ? ((int)$tableOptions['page']) * $table['per_page'] : $table['total'];
41 $this->output->set_content_type('application/json');
42 echo json_encode($table);
43 die;
44 }
45
46 public function create_flag(){
47 $requestData = json_decode(file_get_contents('php://input'), true);
48
49 $this->form_validation->set_data($requestData);
50 $this->form_validation->set_rules('name', 'Flag Name', 'required');
51 $this->form_validation->set_rules('encrypted_name', 'Encrypted Name', 'required');
52
53 if($this->form_validation->run() == false){
54 echo json_encode(['success' => false, 'errors' => array_values($this->form_validation->error_array())]);
55 die;
56 }
57
58 $flagManager = new \BTFlags\FlagManager();
59 $created = $flagManager->create($requestData);
60 if(!$created) die(json_encode(['success' => false, 'errors' => $flagManager->errors()]));
61
62 $log_data = array(
63 'date_created' => ph_time(),
64 'event' => 'Create Flag',
65 'description' => 'Flag Name: ' . $requestData['name'],
66 );
67 $add_log = insert_admin_log($log_data);
68
69 echo json_encode(['success' => true, 'message' => 'Flag successfully created!']);
70 die;
71 }
72
73 /**
74 * Update a flag
75 * @return void
76 */
77 public function update_flag(){
78 $requestData =json_decode(file_get_contents('php://input'), true);
79 $this->form_validation->set_data($requestData);
80 $this->form_validation->set_rules('name', 'Flag Name', 'required');
81 $this->form_validation->set_rules('encrypted_name', 'Encrypted Name', 'required');
82
83 if($this->form_validation->run() == false){
84 echo json_encode(['success' => false, 'errors' => array_values($this->form_validation->error_array())]);
85 die;
86 }
87 $flagManager = new \BTFlags\FlagManager();
88 $updated = $flagManager->update($requestData);
89 if(!$updated) die(json_encode(['success' => false, 'errors' => $flagManager->errors()]));
90
91
92 $log_data = array(
93 'date_created' => ph_time(),
94 'event' => 'Update Flag',
95 'description' => 'Flag Name: ' . $requestData['name'],
96 );
97 $add_log = insert_admin_log($log_data);
98
99 echo json_encode(['success' => true, 'message' => 'Flag successfully updated!']);
100 die;
101 }
102
103 /**
104 * Delete a flag
105 * @return void
106 */
107 public function delete_flag(){
108 $requestData = json_decode(file_get_contents('php://input'), true);
109
110 $flagManager = new \BTFlags\FlagManager();
111 $deleted = $flagManager->delete($requestData);
112 if(!$deleted) die(json_encode(['success' => false, 'errors' => $flagManager->errors()]));
113
114 $log_data = array(
115 'date_created' => ph_time(),
116 'event' => 'Delete Flag',
117 'description' => 'Flag Name: ' . $requestData['name'],
118 );
119 $add_log = insert_admin_log($log_data);
120
121 echo json_encode(['success' => true, 'message' => 'Flag successfully deleted!']);
122 die;
123 }
124
125 }
...\ No newline at end of file ...\ No newline at end of file
1 <?php
2
3 class Flag{
4 public function register(){
5 $flagManager = new \LawFormsUSA\Flags\FlagManager();
6 $flagManager->register();
7 }
8 }
...\ No newline at end of file ...\ No newline at end of file
1 <script>
2 import FlagsService from './../services/Flags';
3 export default {
4 props: {
5 errorPosition: {
6 default: 'static'
7 }
8 },
9 created(){
10 },
11 data(){
12 return {
13 isEditing: false,
14 dialogPostFormVisible: false,
15 selected: null,
16 flag: {
17 name: null,
18 is_encrypted: true,
19 is_filtered: true,
20 encrypted_name: null,
21 has_default: false,
22 default: null,
23 accepted_values: [
24 {
25 value: null,
26 encrypted_value: null
27 }
28 ]
29 },
30 errors: [],
31 tableOptions: {
32 perPage: 10,
33 columns: [
34 {
35 name: 'name',
36 sortField: 'flags.name',
37 title: 'Flag Name',
38 titleClass: 'text-center',
39 dataClass: 'text-left'
40 },
41 {
42 name: 'encrypted_name',
43 sortField: 'flags.encrypted_name',
44 title: 'Encrypted Flag Name',
45 titleClass: 'text-center',
46 dataClass: 'text-left'
47 },
48 {
49 name: 'combined_values',
50 sortField: 'flags.combined_values',
51 title: 'Accepted Values',
52 titleClass: 'text-center',
53 dataClass: 'text-left'
54 },
55 {
56 name: 'default',
57 sortField: 'flags.default',
58 title: 'Default Value',
59 titleClass: 'text-center',
60 dataClass: 'text-left'
61 },
62 {
63 name: '__slot:actions',
64 title: 'Actions',
65 titleClass: 'text-center',
66 dataClass: 'text-center table-padding'
67 }
68 ],
69 css: {
70 tableClass: 'table table-bordered table-hover table-striped',
71 loadingClass: 'loading',
72 ascendingIcon: 'fa fa-caret-up',
73 descendingIcon: 'fa fa-caret-down',
74 detailRowClass: 'vuetable-detail-row',
75 sortHandleIcon: 'grey sidebar icon'
76 }
77 },
78 loading: false,
79 sortOrder: [
80 {
81 field: 'flags.name',
82 direction: 'desc'
83 }
84 ],
85 moreParams: {
86 filter: null,
87 date_range: '',
88 date_from: null,
89 date_to: null,
90 },
91 paginationCss: {
92 wrapperClass: 'col-md-6 text-right',
93 activeClass: 'active large',
94 disabledClass: 'disabled',
95 pageClass: 'item',
96 linkClass: 'icon item',
97 paginationClass: 'pagination-box pagination',
98 paginationInfoClass: 'col-md-6',
99 icons: {
100 first: 'fa fa-angle-double-left',
101 prev: 'fa fa-chevron-left',
102 next: 'fa fa-chevron-right',
103 last: 'fa fa-angle-double-right'
104 }
105 },
106 paginationInfoClass: 'col-md-6',
107 tmpDateTo: null,
108 tmpDateFrom: null,
109 total: 0
110 };
111 },
112 methods: {
113 __capitalize(str){
114 return str.replace(/\b\w/g, (l) => l.toUpperCase());
115 },
116 onPaginationData(paginationData){
117 this.$refs.pagination.setPaginationData(paginationData);
118 this.$refs.paginationInfo.setPaginationData(paginationData);
119 },
120 onChangePage(page){
121 this.$refs.vuetable.changePage(page);
122 },
123 setFilter(){
124 this.moreParams = {
125 filter: this.moreParams.filter
126 };
127
128 this.$nextTick(() => {
129 this.$refs.vuetable.refresh();
130 });
131 },
132 resetFilter(){
133 this.moreParams.filter = '';
134 this.setFilter();
135 },
136 getSortParam: function(sortOrder) {
137 return sortOrder.map(function(sort) {
138 return (sort.direction === 'desc' ? '+' : '') + sort.field
139 }).join(',')
140 },
141 togglePostFormModal(){
142 this.resetFields();
143 this.isEditing = false;
144 this.dialogPostFormVisible = true;
145 },
146 _checkIfFlagIsEncrypted(){
147 if(!this.flag.is_encrypted){
148 this.flag.encrypted_name = this.flag.name
149 _.map(this.flag.accepted_values, (value) => {
150 value.encrypted_value = value.value
151 })
152 }
153 },
154 _checkIfFlagIsFiltered(){
155 if(!this.flag.is_filtered) this.flag.accepted_values = [
156 {
157 value: null,
158 encrypted_value: null
159 }
160 ]
161 },
162 _checkIfFlagHasDefaultValue(){
163 if(this.flag.is_filtered) return false
164 if(!this.flag.has_default) this.flag.default = null;
165 },
166 createFlag(){
167 this.loading = true;
168 this.errors = [];
169 this._checkIfFlagIsFiltered();
170 this._checkIfFlagIsEncrypted();
171 this._checkIfFlagHasDefaultValue();
172 FlagsService.create(this.flag)
173 .then(({data}) => {
174 data = typeof data == 'string' || data instanceof String ? JSON.parse(data) : data;
175
176 if(!data.success){
177 this.loading = false;
178 this.errors = data.errors;
179 return false;
180 }
181
182 this.resetFields();
183 this.dialogPostFormVisible = false;
184 this.loading = false;
185 this.errors = [];
186 this.$notify({
187 title: 'Success',
188 message: data.message,
189 type: 'success'
190 });
191 this.$nextTick(() => {
192 this.$refs.vuetable.reload();
193 });
194 });
195 },
196 toggleEdit(flag){
197 this.showWarningMessage();
198 this.isEditing = true;
199 this.resetFields();
200 this.dialogPostFormVisible = true;
201 this.flag = Object.assign(
202 JSON.parse(JSON.stringify(flag)),
203 {
204 old_name: flag.name,
205 old_encrypted_name: flag.encrypted_name
206 }
207 );
208 },
209 update(){
210 this.loading = true;
211 this.errors = [];
212 this._checkIfFlagIsFiltered();
213 this._checkIfFlagIsEncrypted();
214 this._checkIfFlagHasDefaultValue();
215 FlagsService.update(this.flag)
216 .then(({data}) => {
217 if(!data.success){
218 this.loading = false;
219 this.errors = data.errors;
220 return false;
221 }
222
223 this.resetFields();
224 this.dialogPostFormVisible = false;
225 this.isEditing = false;
226 this.loading = false;
227 this.errors = [];
228 this.$notify({
229 title: 'Success',
230 message: data.message,
231 type: 'success'
232 });
233 this.$nextTick(() => {
234 this.$refs.vuetable.reload();
235 });
236 });
237 },
238 toggleDelete(flag){
239 this.showWarningMessage();
240 this.selected = flag;
241 this.$refs.deleteConfirmationModal.open();
242 },
243 cancelDelete(){
244 this.selected = null;
245 this.$refs.deleteConfirmationModal.close();
246 },
247 remove(){
248 this.loading = true;
249 FlagsService.delete(this.selected)
250 .then(({data}) => {
251 this.loading = false;
252 this.selected = null;
253 this.$refs.deleteConfirmationModal.close();
254
255 if(!data.success){
256 this.$notify({
257 title: 'Error',
258 message: data.message,
259 type: 'error'
260 });
261 return false;
262 }
263
264 this.$notify({
265 title: 'Success',
266 message: data.message,
267 type: 'success'
268 });
269 this.$nextTick(() => {
270 this.$refs.vuetable.reload();
271 });
272 });
273 },
274 resetFields(){
275 this.flag = {
276 name: null,
277 is_encrypted: true,
278 is_filtered: true,
279 encrypted_name: null,
280 has_default: false,
281 default: null,
282 accepted_values: [
283 {
284 value: null,
285 encrypted_value: null
286 }
287 ]
288 }
289 },
290 addFlagValue(){
291 this.flag.accepted_values.push({
292 value: null,
293 encrypted_value: null
294 })
295 },
296 removeFlagValue(index){
297 this.flag.accepted_values.splice(index, 1)
298 },
299 copyNameIfNotEncrypted(){
300 if(!this.flag.is_encrypted) this.flag.encrypted_name = this.flag.name
301 },
302 copyValueOfIfNotEncrypted(acceptedValue){
303 if(!this.flag.is_encrypted) acceptedValue.encrypted_value = acceptedValue.value
304 },
305 showWarningMessage(){
306 this.$refs.moreWarningModal.open();
307 }
308 },
309 watch: {
310 dialogPostFormVisible(to){
311 if(!to) this.errors = [];
312 },
313 'flag.is_filtered'(to, from){
314 this.flag.has_default = false
315 if(!from) this.flag.default = null;
316 },
317 'flag.default'(to, from){
318 if(to) this.flag.has_default = true
319 },
320 'flag.has_default'(to, from){
321 if(!to) this.flag.default = null;
322 }
323 }
324 }
325 </script>
326
327 <template>
328 <div id="lfusa-flag-table">
329 <lfusa-loading-overlay :loading="loading"></lfusa-loading-overlay>
330 <div class="row">
331 <div class="col-md-8">
332 <!-- label / search -->
333 <div class="filter-field">
334 <div class="input-group input-group-sm">
335 <span class="input-group-addon" id="sizing-addon3"><i class="fa fa-search"></i></span>
336 <input type="text" placeholder="Search" class="form-control" id="search" @keypress.enter="setFilter" v-model="moreParams.filter">
337 </div>
338 </div>
339 <!-- /label / search -->
340 <div class="clearfix"></div>
341 </div>
342 <div class="col-md-4">
343 <button type="button" class="btn btn-default btn-success btn-md pull-right" @click="togglePostFormModal">Create</button>
344 </div>
345 </div>
346 <vuetable ref="vuetable"
347 api-url="/admin/api/get_flags_for_table"
348 :fields="tableOptions.columns"
349 :query-params="{ sort: 'sortOrder', page: 'page', perPage: 'perPage' }"
350 :css="tableOptions.css"
351 :sort-order="sortOrder"
352 :per-page="tableOptions.perPage"
353 :append-params="moreParams"
354 pagination-path=""
355 @vuetable:loading="loading = true"
356 @vuetable:load-success="loading = false"
357 @vuetable:pagination-data="onPaginationData">
358 <template slot="actions" scope="props">
359 <div class="table-button-container" v-if="!parseInt(props.rowData.is_default)">
360 <div class="btn-group">
361 <button
362 type="button"
363 class="btn btn-xs btn-success dropdown-toggle"
364 data-toggle="dropdown"
365 aria-haspopup="true"
366 aria-expanded="false">
367 More
368 <span class="caret"></span>
369 </button>
370 <ul class="dropdown-menu">
371 <li>
372 <a href="#" @click="toggleEdit(props.rowData)">
373 <i class="fa fa-pencil"></i>
374 Edit
375 </a>
376 </li>
377 <li>
378 <a href="#" @click="toggleDelete(props.rowData)">
379 <i class="fa fa-trash"></i>
380 Delete
381 </a>
382 </li>
383 </ul>
384 </div>
385 </div>
386 </template>
387 </vuetable>
388 <div class="table-pagination">
389 <vuetable-pagination-info ref="paginationInfo" :css="{ infoClass: 'col-md-6' }"></vuetable-pagination-info>
390 <vuetable-pagination
391 ref="pagination"
392 @vuetable-pagination:change-page="onChangePage"
393 :css="paginationCss"></vuetable-pagination>
394 <div class="clearfix"></div>
395 </div>
396 <el-dialog :title="isEditing ? 'Edit Flag' : 'Create New Flag'" :close-on-click-modal="false" class="field-options-dialog" v-model="dialogPostFormVisible">
397 <form @submit.prevent action="#">
398 <div class="panel panel-danger" :class="errorPosition" v-if="errors && errors.length">
399 <div class="panel-body">
400 <ul>
401 <li class="text-left text-danger error-text" v-for="(error, index) in errors" :key="index">
402 {{ error }}
403 </li>
404 </ul>
405 </div>
406 </div>
407 <div class="form-group">
408 <div class="row">
409 <div class="col-xs-12">
410 <label for="is_encrypted">
411 Encrypted? <el-switch
412 v-model="flag.is_encrypted"
413 on-color="#13ce66"
414 off-color="#ff4949"
415 on-text="Yes"
416 off-text="No"
417 :width="68">
418 </el-switch>
419 </label>
420 <label for="is_encrypted">
421 Filtered? <el-switch
422 v-model="flag.is_filtered"
423 on-color="#13ce66"
424 off-color="#ff4949"
425 on-text="Yes"
426 off-text="No"
427 :width="68">
428 </el-switch>
429 </label>
430 <label for="is_encrypted" v-if="!flag.is_filtered">
431 Has Default Value? <el-switch
432 v-model="flag.has_default"
433 on-color="#13ce66"
434 off-color="#ff4949"
435 on-text="Yes"
436 off-text="No"
437 :width="68">
438 </el-switch>
439 </label>
440 </div>
441 </div>
442 </div>
443 <div class="form-group">
444 <div class="row">
445 <div :class="{ 'col-xs-10': !flag.is_encrypted , 'col-xs-5': flag.is_encrypted, 'col-xs-12': !flag.is_encrypted && !flag.is_filtered}">
446 <label for="flag_name" class="control-label">Flag Name:</label>
447 <input type="text"
448 name="flag_name" id="flag_name"
449 class="form-control interview-control -text round-field"
450 v-model="flag.name"
451 @input="copyNameIfNotEncrypted"
452 autocomplete="off">
453 </div>
454 <div class="encryption-arrows" v-if="flag.is_encrypted">
455 <div class="text-center" style="margin-top: 36px;">
456 <i class="fa fa-arrows-h" style="font-size: 1.5em;font-weight: 300;"></i>
457 </div>
458 </div>
459 <div class="col-xs-5" v-if="flag.is_encrypted">
460 <label for="encrypted_name" class="control-label">Encrypted Flag Name:</label>
461 <input type="text" name="encrypted_name" id="encrypted_name" class="form-control interview-control -text round-field" v-model="flag.encrypted_name" autocomplete="off">
462 </div>
463 </div>
464 </div>
465 <div class="form-group" v-if="flag.has_default && !flag.is_filtered">
466 <div class="row">
467 <div class="col-xs-12">
468 <label for="accepted_value" class="control-label">Default Value:</label>
469 <input type="text"
470 name="accepted_value"
471 id="accepted_value"
472 class="form-control interview-control -text round-field"
473 v-model="flag.default"
474 autocomplete="off">
475 </div>
476 </div>
477 </div>
478 <div class="form-group"
479 v-for="(accepted_value, index) in flag.accepted_values"
480 :key="index"
481 v-if="flag.is_filtered">
482 <div class="row">
483 <div :class="{ 'col-xs-10': !flag.is_encrypted , 'col-xs-5': flag.is_encrypted}">
484 <label for="accepted_value" class="control-label">Value:</label>
485 <label :for="`as_default_${index}`" class="pull-right">
486 Set as Default
487 <input type="checkbox"
488 v-model="flag.default"
489 :true-value="accepted_value.encrypted_value"
490 :false-value="null"
491 :name="`as_default_${index}`"
492 :id="`as_default_${index}`">
493 </label>
494 <input type="text"
495 name="accepted_value"
496 id="accepted_value"
497 class="form-control interview-control -text round-field"
498 v-model="accepted_value.value"
499 @input="copyValueOfIfNotEncrypted(accepted_value)"
500 autocomplete="off">
501 </div>
502 <div class="encryption-arrows" v-if="flag.is_encrypted">
503 <div class="text-center" style="margin-top: 36px;">
504 <i class="fa fa-arrows-h" style="font-size: 1.5em;font-weight: 300;"></i>
505 </div>
506 </div>
507 <div class="col-xs-5" v-if="flag.is_encrypted">
508 <label for="encrypted_accepted_value" class="control-label">Encrypted Value:</label>
509 <input type="text" name="encrypted_accepted_value" id="encrypted_accepted_value" class="form-control interview-control -text round-field" v-model="accepted_value.encrypted_value" autocomplete="off">
510 </div>
511 <div style="margin-top: 26px;">
512 <button type="button"
513 class="btn btn-md btn-success"
514 @click="addFlagValue"
515 v-if="index == (flag.accepted_values.length - 1)">
516 <i class="fa fa-plus"></i>
517 </button>
518 <button type="button"
519 class="btn btn-md btn-danger"
520 @click="removeFlagValue(index)"
521 v-if="index > 0">
522 <i class="fa fa-minus"></i>
523 </button>
524 </div>
525 </div>
526 </div>
527 </form>
528 <span slot="footer" class="dialog-footer">
529 <el-button @click="dialogPostFormVisible = false">Cancel</el-button>
530 <el-button type="primary" @click="createFlag" v-if="!isEditing">Create</el-button>
531 <el-button type="primary" @click="update" v-else>Save</el-button>
532 </span>
533 </el-dialog>
534
535 <sweet-modal icon="warning" ref="deleteConfirmationModal" hide-close-button blocking overlay-theme="dark" modal-theme="dark">
536 Are you sure you want to delete this flag?
537
538 <div slot="button">
539 <button type="button" class="btn btn-md modal-btn -default" @click="cancelDelete" v-if="!loading">No</button>
540 <button type="button" class="btn btn-md modal-btn -danger" @click="remove">Yes</button>
541 </div>
542 </sweet-modal>
543
544 <sweet-modal icon="warning" ref="moreWarningModal" hide-close-button overlay-theme="dark" modal-theme="dark">
545 This may have impact on existing campaigns.
546 </sweet-modal>
547 </div>
548 </template>
549
550 <style lang="sass">
551 #lfusa-flag-table {
552 .interview-control.-text {
553 height: 40px;
554 font-size: 14px;
555 }
556
557 .dropdown-menu{
558 min-width: 90px;
559 }
560
561 .vuetable-pagination-info{
562 .btn-nav.icon.item.disabled:first-child,
563 .btn-nav.icon.item.disabled:nth-child(2){
564 margin-right: -5px;
565 }
566
567 .btn-nav.icon.item.disabled:last-child,
568 .btn-nav.icon.item.disabled:nth-last-child(2){
569 margin-left: -5px;
570 }
571 }
572
573 .encryption-arrows{
574 display: inline-block;
575 float: left;
576 }
577 }
578 </style>
...\ No newline at end of file ...\ No newline at end of file
1 <?php
2 defined('BASEPATH') OR exit('No direct script access allowed');
3
4 /*
5 | -------------------------------------------------------------------------
6 | Hooks
7 | -------------------------------------------------------------------------
8 | This file lets you define "hooks" to extend CI without hacking the core
9 | files. Please see the user guide for info:
10 |
11 | https://codeigniter.com/user_guide/general/hooks.html
12 |
13 */
14 $hook['post_controller_constructor'][] = array(
15 'class' => 'Flag',
16 'function' => 'register',
17 'filename' => 'Flag.php',
18 'filepath' => 'hooks',
19 );
...\ No newline at end of file ...\ No newline at end of file
1 <?php
2
3 if (!function_exists('flag')){
4 function flag($flag, $default = null, $encrypted = false){
5 return \LawFormsUSA\Flags\Flags::get($flag, $encrypted, $default);
6 }
7 }
...\ No newline at end of file ...\ No newline at end of file
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!