facilplan Posted August 17, 2013 Report Posted August 17, 2013 Invoices are not sent. The error message is as follows: Oh noes!Unclose tag, expecting endfor on line 98 in /home/facilpla/public_html/admin/vendors/h2o/h2o/parser.php Has anyone had the same problem? How to fix it?
Michael Posted August 17, 2013 Report Posted August 17, 2013 Not sure what does your system status say? Does it have any warnings?
sanetcy Posted August 17, 2013 Report Posted August 17, 2013 Facilplan I had the same issue but I do not remember if it was my fault or the email template (Unpaid Invoice). An invoice has been created for your account and is attached to this email in PDF format.{% for invoice in invoices %}Invoice #: {invoice.id_code}{% endfor %} Make sure that the {% endfor %} is there Regards Michael 1
Tyson Posted August 17, 2013 Report Posted August 17, 2013 sanetcy's solution should solve this issue. The email template needs to be syntactically correct. You can review the documentation to help with using tags in email templates. Michael 1
facilplan Posted August 18, 2013 Author Report Posted August 18, 2013 Not sure what does your system status say? Does it have any warnings? System status say: Unclose tag, expecting endfor on line 98 in /home/facilpla/public_html/admin/vendors/h2o/h2o/parser.php Send invoices is the only thing that does not work. The welcome email and the creation of the service, it works.
Michael Posted August 19, 2013 Report Posted August 19, 2013 Try using the code from mine, I don't have that issue. <?php class H2o_Lexer { function __construct($options = array()) { $this->options = $options; $trim = ''; if ($this->options['TRIM_TAGS']) $trim = '(?:\r?\n)?'; $this->pattern = ('/\G(.*?)(?:' . preg_quote($this->options['BLOCK_START']). '(.*?)' .preg_quote($this->options['BLOCK_END']) . $trim . '|' . preg_quote($this->options['VARIABLE_START']). '(.*?)' .preg_quote($this->options['VARIABLE_END']) . '|' . preg_quote($this->options['COMMENT_START']). '(.*?)' .preg_quote($this->options['COMMENT_END']) . $trim . ')/sm' ); } function tokenize($source) { $result = new TokenStream; $pos = 0; $matches = array(); preg_match_all($this->pattern, $source, $matches, PREG_SET_ORDER); foreach ($matches as $match) { if ($match[1]) $result->feed('text', $match[1], $pos); $tagpos = $pos + strlen($match[1]); if ($match[2]) $result->feed('block', trim($match[2]), $tagpos); elseif ($match[3]) $result->feed('variable', trim($match[3]), $tagpos); elseif ($match[4]) $result->feed('comment', trim($match[4]), $tagpos); $pos += strlen($match[0]); } if ($pos < strlen($source)){ $result->feed('text', substr($source, $pos), $pos); } $result->close(); return $result; } } class H2o_Parser { var $first; var $storage = array(); var $filename; var $runtime; function __construct($source, $filename, $runtime, $options) { $this->options = $options; //$this->source = $source; $this->runtime = $runtime; $this->filename = $filename; $this->first = true; $this->lexer = new H2o_Lexer($options); $this->tokenstream = $this->lexer->tokenize($source); $this->storage = array( 'blocks' => array(), 'templates' => array(), 'included' => array() ); } function &parse() { $until = func_get_args(); $nodelist = new NodeList($this); while($token = $this->tokenstream->next()) { //$token = $this->tokenstream->current(); switch($token->type) { case 'text' : $node = new TextNode($token->content, $token->position); break; case 'variable' : $args = H2o_Parser::parseArguments($token->content, $token->position); $variable = array_shift($args); $filters = $args; $node = new VariableNode($variable, $filters, $token->position); break; case 'comment' : $node = new CommentNode($token->content); break; case 'block' : if (in_array($token->content, $until)) { $this->token = $token; return $nodelist; } @list($name, $args) = preg_split('/\s+/',$token->content, 2); $node = H2o::createTag($name, $args, $this, $token->position); $this->token = $token; } $this->searching = join(',',$until); $this->first = false; $nodelist->append($node); } if ($until) { throw new TemplateSyntaxError('Unclose tag, expecting '. $until[0]); } return $nodelist; } function skipTo($until) { $this->parse($until); return null; } # Parse arguments static function parseArguments($source = null, $fpos = 0){ $parser = new ArgumentLexer($source, $fpos); $result = array(); $current_buffer = &$result; $filter_buffer = array(); $tokens = $parser->parse(); foreach ($tokens as $token) { list($token, $data) = $token; if ($token == 'filter_start') { $filter_buffer = array(); $current_buffer = &$filter_buffer; } elseif ($token == 'filter_end') { if (count($filter_buffer)) { $i = count($result)-1; if ( is_array($result[$i]) ) $result[$i]['filters'][] = $filter_buffer; else $result[$i] = array(0 => $result[$i], 'filters' => array($filter_buffer)); } $current_buffer = &$result; } elseif ($token == 'boolean') { $current_buffer[] = ($data === 'true'? true : false); } elseif ($token == 'name') { $current_buffer[] = symbol($data); } elseif ($token == 'number' || $token == 'string') { $current_buffer[] = $data; } elseif ($token == 'named_argument') { $last = $current_buffer[count($current_buffer) - 1]; if (!is_array($last)) $current_buffer[] = array(); $namedArgs =& $current_buffer[count($current_buffer) - 1]; list($name,$value) = array_map('trim', explode(':', $data, 2)); # if argument value is variable mark it $value = self::parseArguments($value); $namedArgs[$name] = $value[0]; } elseif( $token == 'operator') { $current_buffer[] = array('operator'=>$data); } } return $result; } } class H2O_RE { static $whitespace, $seperator, $parentheses, $pipe, $filter_end, $operator, $boolean, $number, $string, $i18n_string, $name, $named_args; static function init() { $r = 'strip_regex'; self::$whitespace = '/\s+/m'; self::$parentheses = '/\(|\)/m'; self::$filter_end = '/;/'; self::$boolean = '/true|false/'; self::$seperator = '/,/'; self::$pipe = '/\|/'; self::$operator = '/\s?(>|<|>=|<=|!=|==|!|and |not |or )\s?/i'; self::$number = '/\d+(\.\d*)?/'; self::$name = '/[a-zA-Z_][a-zA-Z0-9-_]*(?:\.[a-zA-Z_0-9][a-zA-Z0-9_-]*)*/'; self::$string = '/(?: "([^"\\\\]*(?:\\\\.[^"\\\\]*)*)" | # Double Quote string \'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\' # Single Quote String )/xsm'; self::$i18n_string = "/_\({$r(self::$string)}\) | {$r(self::$string)}/xsm"; self::$named_args = "{ ({$r(self::$name)})(?:{$r(self::$whitespace)})? : (?:{$r(self::$whitespace)})?({$r(self::$i18n_string)}|{$r(self::$number)}|{$r(self::$name)}) }x"; } } H2O_RE::init(); class ArgumentLexer { private $source; private $match; private $pos = 0, $fpos, $eos; private $operator_map = array( '!' => 'not', '!='=> 'ne', '==' => 'eq', '>' => 'gt', '<' => 'lt', '<=' => 'le', '>=' => 'ge' ); function __construct($source, $fpos = 0){ if (!is_null($source)) $this->source = $source; $this->fpos=$fpos; } function parse(){ $result = array(); $filtering = false; while (!$this->eos()) { $this->scan(H2O_RE::$whitespace); if (!$filtering) { if ($this->scan(H2O_RE::$operator)){ $operator = trim($this->match); if(isset($this->operator_map[$operator])) $operator = $this->operator_map[$operator]; $result[] = array('operator', $operator); } elseif ($this->scan(H2O_RE::$boolean)) $result[] = array('boolean', $this->match); elseif ($this->scan(H2O_RE::$named_args)) $result[] = array('named_argument', $this->match); elseif ($this->scan(H2O_RE::$name)) $result[] = array('name', $this->match); elseif ($this->scan(H2O_RE::$pipe)) { $filtering = true; $result[] = array('filter_start', $this->match); } elseif ($this->scan(H2O_RE::$seperator)) $result[] = array('separator', null); elseif ($this->scan(H2O_RE::$i18n_string)) $result[] = array('string', $this->match); elseif ($this->scan(H2O_RE::$number)) $result[] = array('number', $this->match); else throw new TemplateSyntaxError('unexpected character in filters : "'. $this->source[$this->pos]. '" at '.$this->getPosition()); } else { // parse filters, with chaining and ";" as filter end character if ($this->scan(H2O_RE::$pipe)) { $result[] = array('filter_end', null); $result[] = array('filter_start', null); } elseif ($this->scan(H2O_RE::$seperator)) $result[] = array('separator', null); elseif ($this->scan(H2O_RE::$filter_end)) { $result[] = array('filter_end', null); $filtering = false; } elseif ($this->scan(H2O_RE::$boolean)) $result[] = array('boolean', $this->match); elseif ($this->scan(H2O_RE::$named_args)) $result[] = array('named_argument', $this->match); elseif ($this->scan(H2O_RE::$name)) $result[] = array('name', $this->match); elseif ($this->scan(H2O_RE::$i18n_string)) $result[] = array('string', $this->match); elseif ($this->scan(H2O_RE::$number)) $result[] = array('number', $this->match); else throw new TemplateSyntaxError('unexpected character in filters : "'. $this->source[$this->pos]. '" at '.$this->getPosition()); } } // if we are still in the filter state, we add a filter_end token. if ($filtering) $result[] = array('filter_end', null); return $result; } # String scanner function scan($regexp) { if (preg_match($regexp . 'A', $this->source, $match, null, $this->pos)) { $this->match = $match[0]; $this->pos += strlen($this->match); return true; } return false; } function eos() { return $this->pos >= strlen($this->source); } /** * return the position in the template */ function getPosition() { return $this->fpos + $this->pos; } } ?>
facilplan Posted August 19, 2013 Author Report Posted August 19, 2013 Facilplan I had the same issue but I do not remember if it was my fault or the email template (Unpaid Invoice). An invoice has been created for your account and is attached to this email in PDF format. {% for invoice in invoices %} Invoice #: {invoice.id_code}{% endfor %} Make sure that the {% endfor %} is there Regards Thank you Sanecty for your answer, but I think that I don't explained properly. When I create an invoice manually, then I can "see" "edit" "pay" options, but the Status is "Unsent". When I click the "send" button the system tells me: Oh noes!Unclose tag, expecting endfor on line 98 in /home/facilpla/public_html/admin/vendors/h2o/h2o/parser.php Printing Stack Trace: #0 /home/facilpla/public_html/admin/vendors/h2o/h2o/tags.php(112): H2o_Parser->parse('endfor', 'else') #1 /home/facilpla/public_html/admin/vendors/h2o/h2o.php(151): For_Tag->__construct('invoice in invo...', Object(H2o_Parser), 111) #2 /home/facilpla/public_html/admin/vendors/h2o/h2o/parser.php(89): H2o::createTag('for', 'invoice in invo...', Object(H2o_Parser), 111) #3 /home/facilpla/public_html/admin/vendors/h2o/h2o.php(106): H2o_Parser->parse() #4 /home/facilpla/public_html/admin/vendors/h2o/h2o.php(142): H2o->parse('Hola {contact.f...') #5 /home/facilpla/public_html/admin/app/models/emails.php(753): H2o::parseString('Hola {contact.f...', Array) #6 /home/facilpla/public_html/admin/app/models/emails.php(479): Emails->buildEmail('invoice_deliver...', '1', 'es_es', Array) #7 /home/facilpla/public_html/admin/components/invoice_delivery/invoice_delivery.php(122): Emails->send('invoice_deliver...', '1', 'es_es', 'contacto@mundod...', Array, NULL, NULL, Array, Array) #8 /home/facilpla/public_html/admin/app/controllers/admin_clients.php(294): InvoiceDelivery->deliverInvoices(Array, 'email', 'contacto@mundod...', '1', Array) #9 /home/facilpla/public_html/admin/lib/dispatcher.php(111): AdminClients->invoices() #10 /home/facilpla/public_html/admin/index.php(21): Dispatcher::dispatch('/admin/clients/...') #11 {main} This problem only happens in "send invoice" because the welcome message arrives correctly.
facilplan Posted August 19, 2013 Author Report Posted August 19, 2013 Try using the code from mine, I don't have that issue. <?php class H2o_Lexer { function __construct($options = array()) { $this->options = $options; $trim = ''; if ($this->options['TRIM_TAGS']) $trim = '(?:\r?\n)?'; $this->pattern = ('/\G(.*?)(?:' . preg_quote($this->options['BLOCK_START']). '(.*?)' .preg_quote($this->options['BLOCK_END']) . $trim . '|' . preg_quote($this->options['VARIABLE_START']). '(.*?)' .preg_quote($this->options['VARIABLE_END']) . '|' . preg_quote($this->options['COMMENT_START']). '(.*?)' .preg_quote($this->options['COMMENT_END']) . $trim . ')/sm' ); } function tokenize($source) { $result = new TokenStream; $pos = 0; $matches = array(); preg_match_all($this->pattern, $source, $matches, PREG_SET_ORDER); foreach ($matches as $match) { if ($match[1]) $result->feed('text', $match[1], $pos); $tagpos = $pos + strlen($match[1]); if ($match[2]) $result->feed('block', trim($match[2]), $tagpos); elseif ($match[3]) $result->feed('variable', trim($match[3]), $tagpos); elseif ($match[4]) $result->feed('comment', trim($match[4]), $tagpos); $pos += strlen($match[0]); } if ($pos < strlen($source)){ $result->feed('text', substr($source, $pos), $pos); } $result->close(); return $result; } } class H2o_Parser { var $first; var $storage = array(); var $filename; var $runtime; function __construct($source, $filename, $runtime, $options) { $this->options = $options; //$this->source = $source; $this->runtime = $runtime; $this->filename = $filename; $this->first = true; $this->lexer = new H2o_Lexer($options); $this->tokenstream = $this->lexer->tokenize($source); $this->storage = array( 'blocks' => array(), 'templates' => array(), 'included' => array() ); } function &parse() { $until = func_get_args(); $nodelist = new NodeList($this); while($token = $this->tokenstream->next()) { //$token = $this->tokenstream->current(); switch($token->type) { case 'text' : $node = new TextNode($token->content, $token->position); break; case 'variable' : $args = H2o_Parser::parseArguments($token->content, $token->position); $variable = array_shift($args); $filters = $args; $node = new VariableNode($variable, $filters, $token->position); break; case 'comment' : $node = new CommentNode($token->content); break; case 'block' : if (in_array($token->content, $until)) { $this->token = $token; return $nodelist; } @list($name, $args) = preg_split('/\s+/',$token->content, 2); $node = H2o::createTag($name, $args, $this, $token->position); $this->token = $token; } $this->searching = join(',',$until); $this->first = false; $nodelist->append($node); } if ($until) { throw new TemplateSyntaxError('Unclose tag, expecting '. $until[0]); } return $nodelist; } function skipTo($until) { $this->parse($until); return null; } # Parse arguments static function parseArguments($source = null, $fpos = 0){ $parser = new ArgumentLexer($source, $fpos); $result = array(); $current_buffer = &$result; $filter_buffer = array(); $tokens = $parser->parse(); foreach ($tokens as $token) { list($token, $data) = $token; if ($token == 'filter_start') { $filter_buffer = array(); $current_buffer = &$filter_buffer; } elseif ($token == 'filter_end') { if (count($filter_buffer)) { $i = count($result)-1; if ( is_array($result[$i]) ) $result[$i]['filters'][] = $filter_buffer; else $result[$i] = array(0 => $result[$i], 'filters' => array($filter_buffer)); } $current_buffer = &$result; } elseif ($token == 'boolean') { $current_buffer[] = ($data === 'true'? true : false); } elseif ($token == 'name') { $current_buffer[] = symbol($data); } elseif ($token == 'number' || $token == 'string') { $current_buffer[] = $data; } elseif ($token == 'named_argument') { $last = $current_buffer[count($current_buffer) - 1]; if (!is_array($last)) $current_buffer[] = array(); $namedArgs =& $current_buffer[count($current_buffer) - 1]; list($name,$value) = array_map('trim', explode(':', $data, 2)); # if argument value is variable mark it $value = self::parseArguments($value); $namedArgs[$name] = $value[0]; } elseif( $token == 'operator') { $current_buffer[] = array('operator'=>$data); } } return $result; } } class H2O_RE { static $whitespace, $seperator, $parentheses, $pipe, $filter_end, $operator, $boolean, $number, $string, $i18n_string, $name, $named_args; static function init() { $r = 'strip_regex'; self::$whitespace = '/\s+/m'; self::$parentheses = '/\(|\)/m'; self::$filter_end = '/;/'; self::$boolean = '/true|false/'; self::$seperator = '/,/'; self::$pipe = '/\|/'; self::$operator = '/\s?(>|<|>=|<=|!=|==|!|and |not |or )\s?/i'; self::$number = '/\d+(\.\d*)?/'; self::$name = '/[a-zA-Z_][a-zA-Z0-9-_]*(?:\.[a-zA-Z_0-9][a-zA-Z0-9_-]*)*/'; self::$string = '/(?: "([^"\\\\]*(?:\\\\.[^"\\\\]*)*)" | # Double Quote string \'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\' # Single Quote String )/xsm'; self::$i18n_string = "/_\({$r(self::$string)}\) | {$r(self::$string)}/xsm"; self::$named_args = "{ ({$r(self::$name)})(?:{$r(self::$whitespace)})? : (?:{$r(self::$whitespace)})?({$r(self::$i18n_string)}|{$r(self::$number)}|{$r(self::$name)}) }x"; } } H2O_RE::init(); class ArgumentLexer { private $source; private $match; private $pos = 0, $fpos, $eos; private $operator_map = array( '!' => 'not', '!='=> 'ne', '==' => 'eq', '>' => 'gt', '<' => 'lt', '<=' => 'le', '>=' => 'ge' ); function __construct($source, $fpos = 0){ if (!is_null($source)) $this->source = $source; $this->fpos=$fpos; } function parse(){ $result = array(); $filtering = false; while (!$this->eos()) { $this->scan(H2O_RE::$whitespace); if (!$filtering) { if ($this->scan(H2O_RE::$operator)){ $operator = trim($this->match); if(isset($this->operator_map[$operator])) $operator = $this->operator_map[$operator]; $result[] = array('operator', $operator); } elseif ($this->scan(H2O_RE::$boolean)) $result[] = array('boolean', $this->match); elseif ($this->scan(H2O_RE::$named_args)) $result[] = array('named_argument', $this->match); elseif ($this->scan(H2O_RE::$name)) $result[] = array('name', $this->match); elseif ($this->scan(H2O_RE::$pipe)) { $filtering = true; $result[] = array('filter_start', $this->match); } elseif ($this->scan(H2O_RE::$seperator)) $result[] = array('separator', null); elseif ($this->scan(H2O_RE::$i18n_string)) $result[] = array('string', $this->match); elseif ($this->scan(H2O_RE::$number)) $result[] = array('number', $this->match); else throw new TemplateSyntaxError('unexpected character in filters : "'. $this->source[$this->pos]. '" at '.$this->getPosition()); } else { // parse filters, with chaining and ";" as filter end character if ($this->scan(H2O_RE::$pipe)) { $result[] = array('filter_end', null); $result[] = array('filter_start', null); } elseif ($this->scan(H2O_RE::$seperator)) $result[] = array('separator', null); elseif ($this->scan(H2O_RE::$filter_end)) { $result[] = array('filter_end', null); $filtering = false; } elseif ($this->scan(H2O_RE::$boolean)) $result[] = array('boolean', $this->match); elseif ($this->scan(H2O_RE::$named_args)) $result[] = array('named_argument', $this->match); elseif ($this->scan(H2O_RE::$name)) $result[] = array('name', $this->match); elseif ($this->scan(H2O_RE::$i18n_string)) $result[] = array('string', $this->match); elseif ($this->scan(H2O_RE::$number)) $result[] = array('number', $this->match); else throw new TemplateSyntaxError('unexpected character in filters : "'. $this->source[$this->pos]. '" at '.$this->getPosition()); } } // if we are still in the filter state, we add a filter_end token. if ($filtering) $result[] = array('filter_end', null); return $result; } # String scanner function scan($regexp) { if (preg_match($regexp . 'A', $this->source, $match, null, $this->pos)) { $this->match = $match[0]; $this->pos += strlen($this->match); return true; } return false; } function eos() { return $this->pos >= strlen($this->source); } /** * return the position in the template */ function getPosition() { return $this->fpos + $this->pos; } } ?> Well, I tried your script but it is identical to mine and the result is the same. Is there a problem in line 98?
Michael Posted August 19, 2013 Report Posted August 19, 2013 Well, I tried your script but it is identical to mine and the result is the same. Is there a problem in line 98? I'm not sure as I'm only learning to make modules (not going well at the moment haha) But this is the code and it seems to be coming from above it if your right because this is to spit an error out. if ($until) { throw new TemplateSyntaxError('Unclose tag, expecting '. $until[0]); }
Tyson Posted August 19, 2013 Report Posted August 19, 2013 Go to [settings] -> [Emails] -> and click to edit "Invoice Delivery (Unpaid)". Paste the contents of the HTML and text versions of the email template here. Your error points to a missing {% endfor %} tag, and sanetcy's solution should resolve it: Facilplan I had the same issue but I do not remember if it was my fault or the email template (Unpaid Invoice). An invoice has been created for your account and is attached to this email in PDF format.{% for invoice in invoices %}Invoice #: {invoice.id_code}{% endfor %} Make sure that the {% endfor %} is there Regards facilplan 1
Cody Posted August 20, 2013 Report Posted August 20, 2013 Closing as not a bug. CORE-683 and CORE-684 address this issue by checking syntax of the template when editing and by allowing undefined variables in templates respectively.
Recommended Posts