<?php
namespace com\whitemagicsoftware;
require_once "constants.php";
require_once "class.BaseController.php";
require_once "class.SecureImageUpload.php";
require_once "class.Scan.php";
class Recipe extends BaseController {
private $scannedText = null;
private $title = null;
function __construct() {
parent::__construct();
}
protected function exists( $id ) {
return $this->isTrue( $this->call( "is_existing_recipe", "exists", $id ) );
}
private function create() {
$result = $this->call( "create_recipe", "id", $this->getAccountId() );
return isset( $result[0] ) ? $result[0]["id"] : 0;
}
private function annihilate() {
$this->call( "delete_recipe", "", $this->getId(), $this->getAccountId() );
}
protected function getLastResortId() {
$auth_id = $this->getAuthenticationId();
$result = $this->call( "get_recent_recipe_id", "id", $auth_id );
$result = isset( $result[0] ) ? $result[0]["id"] : 0;
if( $result <= 0 ) {
$result = $this->create();
}
return $result;
}
protected function getParameterIdName() {
return "recipe-id";
}
private function getTitle() {
if( $this->title == null ) {
$result = $this->call( "get_recipe_title", "title", $this->getId() );
$this->title = $result ? $result[0]["title"] : "untitled";
}
return $this->title;
}
private function getInstruction( $seq ) {
$result = $this->call( "get_recipe_instruction", "instruction",
$this->getId(),
$this->getParameterId( "seq", 0 )
);
return $result ? $result[0]["instruction"] : "";
}
protected function getStylesheetName() {
return "xsl/recipe.xsl";
}
protected function getAuthorizationFunctionName() {
return "is_authorized_recipe";
}
private function getXml() {
$result = $this->call( "generate_recipe_xml", "x", $this->getId() );
return isset( $result ) ? $result[0]["x"] : $this->getErrorXml();
}
protected function getXhtml() {
$xslt = $this->getXsltEngine();
$xslt->setXml( $this->getXml() );
$xslt->setStylesheet( $this->getStylesheetName() );
$xslt->setParameter( $this->getParameterIdName(), $this->getId() );
$xslt->setParameter( "editable", $this->isEditable() );
$xslt->setParameter( "cookie", $this->getCookieToken() );
$xslt->setParameter( "ingredient-text", $this->getScannedText() );
return $xslt->transform();
}
private function setScannedText( $scannedText ) {
$this->scannedText = str_replace( "\"", "''", $scannedText );
}
private function getScannedText() {
return $this->scannedText;
}
private function updatePhotograph() {
$uploader = new SecureImageUpload();
$id = $this->getAccountId();
$appId = "recipe/" . $this->getId();
if( $uploader->handle( "photograph-image", $id, $appId ) ) {
$url = $uploader->getURL();
$this->call( "recipe_photograph_upsert", "", $this->getId(), $url );
}
$this->call( "recipe_photograph_citation_upsert", "", $this->getId(),
$this->getParameter( "photograph-author-name" ),
$this->getParameterURL( "photograph-author-url" )
);
}
private function updateCitation() {
$this->call( "recipe_citation_upsert", "", $this->getId(),
$this->getParameter( "recipe-author-name" ),
$this->getParameterURL( "recipe-author-url" ),
$this->getParameterId( "recipe-attribution-id", 0 )
);
}
private function scan() {
$uploader = new SecureImageUpload();
$id = $this->getAccountId();
$appId = "scan/" . $this->getId();
if( $uploader->handle( "scan-image", $id, $appId ) ) {
$filename = $uploader->getFilename();
$scanner = new Scan();
$this->setScannedText( $scanner->distill( $filename ) );
}
}
private function insertSubstitute() {
$original_id = $this->getParameterId( "ingredient-substitute-original" );
$substitute = $this->getParameter( "ingredient-substitute-alternative" );
$ingredient = json_decode( $this->parseIngredientText( $substitute ) );
$alternative_id = $this->parseIngredient( $ingredient );
if( $alternative_id > 0 ) {
$this->call( "substitute_insert", "",
$this->getId(), $original_id, $alternative_id );
}
}
private function upsertPreparation( $temperature, $unit ) {
if( is_numeric( $temperature ) ) {
$this->call( "preparation_upsert", "",
$this->getId(), $temperature, $unit );
}
}
private function deletePreparation() {
$this->call( "preparation_delete", "", $this->getId() );
}
private function insertIngredientGroup( $group_name ) {
$this->call( "ingredient_group_insert", "",
$this->getId(), $group_name );
}
private function upsertInstructions( $instruction_group_id, $steps ) {
$steps = $this->toArray( array_filter( explode( "\n", $steps ) ) );
$this->call( "instructions_insert", "", $instruction_group_id, $steps );
}
private function insertTag( $tag ) {
$this->call( "tag_insert", "", $this->getId(), $tag );
}
private function deleteTag( $tag ) {
$this->call( "tag_delete", "", $this->getId(), $tag );
}
private function deleteInstruction( $instruction_id ) {
$this->call( "instruction_delete", "",
$this->getId(), $instruction_id );
}
private function createInstructionGroup() {
$this->call( "instruction_group_create", "", $this->getId() );
}
private function deleteInstructionGroup( $group_id ) {
$this->call( "instruction_group_delete", "", $group_id );
}
private function moveInstructionGroupAbove( $group_id ) {
$this->call( "instruction_group_move", "",
$this->getId(), $group_id, true );
}
private function moveInstructionGroupBelow( $group_id ) {
$this->call( "instruction_group_move", "",
$this->getId(), $group_id, false );
}
private function upsertEquipment() {
}
private function deleteEquipment() {
$this->call( "equipment_delete", "",
$this->getParameterId( "delete_equipment" ) );
}
private function upsertIngredient() {
$ingredientName = $this->getParameter( "ingredient-name" );
if( isset( $ingredientName ) ) {
$this->call( "ingredient_upsert", "",
$this->getId( "ingredient-group-id" ),
$ingredientName,
$this->getParameter( "ingredient-unit", 0 ),
$this->getParameter( "ingredient-min", 0 ),
$this->getParameter( "ingredient-max", 0 ),
$this->getParameter( "ingredient-alias" ),
$this->getParameter( "ingredient-condition" ),
$this->getParameter( "ingredient-optional", 1 ) );
}
}
private function moveIngredient() {
$ingredient = $this->getParameterId( "ingredient" );
$group = $this->getParameterId( "group" );
$index = $this->getParameterId( "index" );
if( $ingredient > 0 && $group > 0 && $index >= 0 ) {
$this->call( "ingredient_move", "", $ingredient, $group, $index );
}
}
private function setTitle( $title ) {
global $DEFAULT_RECIPE_TITLE;
$result = $this->call( "set_recipe_title", "title",
$this->getId(), $title );
return $result ? $result[0]["title"] : $DEFAULT_RECIPE_TITLE;
}
private function setInstructionGroupLabel( $id, $old, $new ) {
global $DEFAULT_INSTRUCTIONS_TITLE;
$result = $this->call( "set_instruction_group_label", "label",
$id, $old, $new );
return $result ? $result[0]['label'] : $DEFAULT_INSTRUCTIONS_TITLE;
}
private function setIngredientGroupLabel( $id, $old, $new ) {
global $DEFAULT_INGREDIENTS_TITLE;
$result = $this->call( "set_ingredient_group_label", "label",
$id, $old, $new );
return $result ? $result[0]['label'] : $DEFAULT_INGREDIENTS_TITLE;
}
private function insertInstruction( $text ) {
$this->call( "instruction_insert", "", $this->getId(), $text );
}
private function insertEquipment( $equipment ) {
$this->call( "equipment_insert", "", $this->getId(), $equipment );
}
public function parseInstructionText( $text ) {
global $SERVICE_NLP;
$text = str_replace( PHP_EOL, ". ", $text );
$curl_parameters = array(
'q' => $text,
'nlp' => 's'
);
$curl_options = array(
CURLOPT_URL => $SERVICE_NLP,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query( $curl_parameters ),
CURLOPT_HTTP_VERSION => 1.0,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false
);
$curl = curl_init();
curl_setopt_array( $curl, $curl_options );
$result = curl_exec( $curl );
curl_close( $curl );
return $result;
}
private function parseInstructions( $line ) {
$instructions = json_decode( $line, true );
foreach( $instructions as $sentence ) {
$this->parseInstruction( $sentence );
}
}
private function parseInstruction( $sentence ) {
$step = "";
$parens = false;
$temperature = null;
foreach( $sentence as $words ) {
$preheat = false;
foreach( $words as $word ) {
$tag = $word["pos"];
if( $tag === "Fp" ) {
continue;
}
else if( $tag === "Fpa" ) {
$parens = true;
}
else if( $tag === "Fpt" ) {
$parens = false;
}
if( $parens ) {
continue;
}
$form = trim( str_replace( '_', ' ', $word['form'] ) );
$lemma = trim( str_replace( '_', ' ', $word['lemma'] ) );
if( strcasecmp( $lemma, 'fuck' ) == 0 ) {
if( strcasecmp( $form, 'fucker' ) == 0 ) {
$form = 'thing';
}
else if( strcasecmp( $form, 'fuckers' ) == 0 ) {
$form = 'things';
}
else {
continue;
}
}
if( strcasecmp( $lemma, 'preheat' ) == 0 ) {
$preheat = true;
}
if( $this->startsWith( $tag, "Z" ) && $preheat ) {
$temperature = $form;
continue;
}
$space = " ";
if( ($step && $this->startsWith( $tag, "F" )) || $tag === "POS" ) {
$space = "";
}
else if( $this->startsWith( $tag, "N" ) ) {
$this->insertEquipment( $lemma );
}
if( !$preheat ) {
$step = "$step$space$form";
}
}
}
if( $step ) {
$step = str_replace( " 's", "'s", $step );
$this->insertInstruction( $step );
}
if( $temperature && $preheat ) {
$this->upsertPreparation( $temperature, 'F' );
}
}
public function parseIngredientText( $text ) {
global $SERVICE_NLP;
$curl_parameters = array(
'q' => $text,
'nlp' => 'p'
);
$curl_options = array(
CURLOPT_URL => $SERVICE_NLP,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query( $curl_parameters ),
CURLOPT_HTTP_VERSION => 1.0,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => false
);
$curl = curl_init();
curl_setopt_array( $curl, $curl_options );
$result = curl_exec( $curl );
curl_close( $curl );
return $result;
}
private function setIngredientRecipeText( $text ) {
$result = $this->parseIngredientText( $text );
if( isset( $result ) ) {
$line_array = preg_split( '/(\r?\n)/', trim( $result ) );
foreach( $line_array as $line ) {
if( !empty( $line ) ) {
$decoded = json_decode( $line );
if( $decoded ) {
$this->parseIngredient( $decoded );
}
}
}
}
}
private function parseSplitIngredientText( $decoded ) {
$ingredient_start = 0;
$ingredient_end = 0;
$nouns = 0;
$prevNoun = false;
foreach( $decoded as $token ) {
$noun = $this->startsWith( $token->pos, "N" );
if( $noun ) {
if( $prevNoun ) {
continue;
}
else {
$nouns++;
if( $nouns > 1 ) {
break;
}
}
}
$prevNoun = $noun;
}
if( $nouns > 1 ) {
foreach( $decoded as $token ) {
$ingredient_end++;
if( $this->startsWith( $token->pos, "N" ) ) {
$length = $ingredient_end - $ingredient_start;
$this->parseIngredient(
array_slice( $decoded, $ingredient_start, $length ) );
$ingredient_start = $ingredient_end;
}
}
}
else if( $nouns == 1 ) {
$this->parseIngredient( $decoded );
}
}
private function parseIngredient( $decoded ) {
$ingredient = $this->extractIngredient( $decoded );
return $this->insertIngredient( $ingredient );
}
public function extractIngredient( $decoded ) {
$result = array();
if( isset( $decoded ) && !empty( $decoded ) ) {
list( $min, $max ) = $this->extractIngredientMeasures( $decoded );
$unit = $this->extractIngredientUnit( $decoded );
$ingredient = $this->extractIngredientName( $decoded );
$condition = $this->extractIngredientCondition( $decoded, $ingredient );
$result = array(
'name'=>$ingredient,
'unit'=>$unit,
'min'=>$min,
'max'=>$max,
'condition'=>$condition );
}
return $result;
}
public function extractIngredientName( $decoded,
$includeAdjectives = true,
$includeVerbs = true ) {
$ingredient = "";
$matching = false;
$parens = false;
foreach( $decoded as $token ) {
if( ($token->pos === "Fpa") ||
($token->pos === "Fca") ||
($token->pos === "Fla") ) {
$parens = true;
}
else
if( ($token->pos === "Fpt") ||
($token->pos === "Fct") ||
($token->pos === "Flt" ) ) {
$parens = false;
}
if( $parens || ($token->form === "a") || ($token->lemma === "fuck") ||
($token->lemma === "handful") ) {
continue;
}
$found = $this->startsWith( $token->pos, "N" );
if( $includeAdjectives ) {
$found = $found || $this->startsWith( $token->pos, "J" );
}
if( $includeVerbs ) {
$found = $found || $this->startsWith( $token->pos, "VB" );
$verb_pos = array( "VBD", "VBN", "VBP" );
if( in_array( $token->pos, $verb_pos ) ) {
continue;
}
}
if( $token->lemma === "heap" ) {
continue;
}
if( !$found && $matching ) {
break;
}
if( $found ) {
$ingredient = "$ingredient $token->lemma";
$matching = true;
}
}
if( $ingredient ) {
$ingredient = trim( str_replace( "_", " ", $ingredient ) );
}
return $ingredient;
}
public function extractIngredientMeasures( $decoded, $default_min = 1 ) {
$min = null;
$max = 0;
$number = 0;
$parens = false;
$matching = false;
$found = false;
if( !isset( $decoded ) ) {
$decoded = array();
}
foreach( $decoded as $token ) {
if( ($token->pos === "Fpa") ||
($token->pos === "Fca") ||
($token->pos === "Fla") ) {
$parens = true;
}
else
if( ($token->pos === "Fpt") ||
($token->pos === "Fct") ||
($token->pos === "Flt" ) ) {
$parens = false;
}
if( $parens || ($token->form === "a") ) {
continue;
}
if( $token->lemma === "couple" ) {
$min = 2;
$max = 0;
break;
} else if( $token->lemma === "few" ) {
$min = 3;
$max = 4;
break;
} else if( $token->lemma === "handful" ) {
$min = 5;
$max = 6;
break;
} else if( $token->lemma === "several" ) {
$min = 6;
$max = 8;
break;
}
$found = $token->pos === "Z" ||
$token->pos === "Zd" ||
$token->pos === "Zu" ||
$token->pos === "Fz";
if( !$found && $matching ) {
if( $min == null ) {
$min = $number;
$matching = false;
$number = 0;
}
else {
$max = $number;
break;
}
}
else if( $found ) {
if( $matching ) {
$number += $this->parseNumber( $token->lemma );
}
else {
$number = $this->parseNumber( $token->lemma );
$matching = true;
}
}
}
if( $min == null || $min == 0 ) {
if( $number ) {
$min = $number;
}
else {
$min = $default_min;
}
}
if( $max == null ) {
$max = 0;
}
return array( $min, $max );
}
public function extractIngredientUnit( $decoded ) {
$unit = null;
$parens = false;
foreach( $decoded as $token ) {
if( ($token->pos === "Fpa") ||
($token->pos === "Fca") ||
($token->pos === "Fla") ) {
$parens = true;
}
else
if( ($token->pos === "Fpt") ||
($token->pos === "Fct") ||
($token->pos === "Flt" ) ) {
$parens = false;
}
if( $token->lemma === "taste" ) {
$unit = "pinch";
break;
}
if( $parens ) {
continue;
}
if( $token->pos === "Zp" ) {
$unit = "$token->lemma";
break;
}
}
return $unit;
}
public function extractIngredientCondition( $decoded, $ingredient ) {
$condition = null;
$adverb = "";
$blacklist =
"need paste taste vary use do be prepare want sweet pit hydrogenate flake desire granulate prefer garnish";
foreach( $decoded as $token ) {
$lemma = $token->lemma;
if( strpos( $blacklist, $lemma ) !== false ) {
continue;
}
if( !strncmp( $ingredient, $lemma, strlen( $lemma ) ) ) {
continue;
}
if( $token->pos === "RB" ) {
$adverb = "$lemma ";
continue;
}
if( preg_match( "/^VB[NDP]/", $token->pos ) > 0 ) {
if( isset( $condition ) ) {
$condition = "$condition,";
}
$condition = "$condition$adverb$lemma";
}
$adverb = "";
}
return $condition;
}
private function insertIngredient( $ingredient ) {
$result = $this->call( "ingredient_insert", "id", $this->getId(),
$ingredient["name"],
$ingredient["unit"],
$ingredient["min"],
$ingredient["max"],
$ingredient["condition"] );
return isset( $result ) ? $result[0]["id"] : -1;
}
private function updateIngredients() {
$ingredientId = $this->getParameterId( "delete_ingredient" );
$ingredientGroupId = $this->getParameterId( "delete_ingredient_group" );
$createGroup = $this->getParameterId( "create_ingredient_group" );
if( $ingredientId > 0 ) {
$this->call( "ingredient_delete", "", $ingredientId );
}
if( $ingredientGroupId > 0 ) {
$this->call( "ingredient_group_delete", "", $ingredientGroupId );
}
if( !empty( $createGroup ) ) {
$this->call( "ingredient_group_create", "", $this->getId() );
}
}
private function parseNumber( $number ) {
if( $number === "½" ) { $number = "1/2"; } else
if( $number === "⅓" ) { $number = "1/3"; } else
if( $number === "⅔" ) { $number = "2/3"; } else
if( $number === "¼" ) { $number = "1/4"; } else
if( $number === "¾" ) { $number = "3/4"; } else
if( $number === "⅕" ) { $number = "1/5"; } else
if( $number === "⅖" ) { $number = "2/5"; } else
if( $number === "⅗" ) { $number = "3/5"; } else
if( $number === "⅘" ) { $number = "4/5"; } else
if( $number === "⅙" ) { $number = "1/6"; } else
if( $number === "⅚" ) { $number = "5/6"; } else
if( $number === "⅛" ) { $number = "1/8"; } else
if( $number === "⅜" ) { $number = "3/8"; } else
if( $number === "⅝" ) { $number = "5/8"; } else
if( $number === "⅞" ) { $number = "7/8"; }
$result = 1;
$number = "$number/";
list( $numerator, $denomenator ) = explode( "/", $number );
if( $denomenator > 0 ) {
$result = (float)$numerator / (float)$denomenator;
}
else {
$result = (float)$number;
}
return floor( $result * 1000 ) / 1000;
}
protected function handleRequest() {
$id = $this->getId();
$command = $this->getCommand();
$subcommand = $this->getSubCommand();
$prg = true;
if( $command === "create" && $subcommand === "recipe" ) {
$id = $this->create();
if( $id != $this->getId() ) {
global $BASE_RECIPE;
if( $this->redirect( $BASE_RECIPE, $id, $this->getTitle() ) ) {
return;
}
}
}
if( $command === "delete" && $subcommand === "recipe" ) {
global $BASE_ACCOUNT;
$this->annihilate();
if( $this->redirect( $BASE_ACCOUNT, $this->getAccountId(), "" ) ) {
return;
}
}
if( $command === "ingredients" ) {
if( $subcommand === "accept" ) {
$ingredients = $this->getParameter( "ingredients-list" );
if( !empty( $ingredients ) ) {
$this->setIngredientRecipeText( $ingredients );
}
}
else if( $subcommand === "update" ) {
$this->updateIngredients();
}
}
if( $command === "update" ) {
$old = $this->getParameter( "original_html" );
$new = $this->getParameter( "update_value" );
if( $subcommand === "title" ) {
echo $this->setTitle( $new );
}
elseif( $subcommand === "instruction.group" ) {
$id = $this->getParameterId( "instruction-group-id" );
echo $this->setInstructionGroupLabel( $id, $old, $new );
}
elseif( $subcommand === "ingredient.group" ) {
$id = $this->getParameterId( "ingredient-group-id" );
echo $this->setIngredientGroupLabel( $id, $old, $new );
}
return;
}
if( $command === "substitute" ) {
if( $subcommand === "accept" ) {
$this->insertSubstitute();
}
}
elseif( $command === "preparation" ) {
$temperature = $this->getParameter( "preparation-temperature" );
$unit = $this->getParameter( "preparation-unit" );
if( $subcommand === "accept" ) {
$this->upsertPreparation( $temperature, $unit );
}
elseif( $subcommand === "delete" ) {
$this->deletePreparation();
}
}
elseif( $command === "ingredient" ) {
if( $subcommand === "accept" ) {
$this->upsertIngredient();
}
else if( $subcommand === "move" ) {
$this->moveIngredient();
}
}
elseif( $command === "ingredient.group" ) {
$group_name = $this->getParameter( "ingredient-group-name" );
if( $subcommand === "accept" ) {
$this->insertIngredientGroup( $group_name );
}
}
elseif( $command === "instruction" ) {
$instruction_group_id =
$this->getParameterId( "instructions_group_id", 1 );
$steps = $this->getParameter( "steps" );
if( $subcommand === "accept" ) {
$this->upsertInstructions( $instruction_group_id, $steps );
}
}
elseif( $command === "instruction.group" ) {
$group_name = $this->getParameter( "instruction-group-name" );
$group_id = $this->getParameterId( "instruction_group-id" );
if( $subcommand === "update" ) {
$group_id = $this->getParameter( "delete_instruction_group" );
if( is_numeric( $group_id ) ) {
$this->deleteInstructionGroup( $group_id );
}
else {
$group_id = $this->getParameter( "create_instruction_group" );
if( is_numeric( $group_id ) ) {
$this->createInstructionGroup();
}
}
}
elseif( $subcommand === "move.above" ) {
$this->moveInstructionGroupAbove( $group_id );
}
elseif( $subcommand === "move.below" ) {
$this->moveInstructionGroupBelow( $group_id );
}
}
elseif( $command === "equipment" ) {
if( $subcommand === "accept" ) {
$this->upsertEquipment();
}
elseif( $subcommand === "delete" ) {
$this->deleteEquipment();
}
}
elseif( $command === "tag" ) {
$tag = $this->getParameter( "tag" );
if( $subcommand === "add" ) {
$this->insertTag( $tag );
}
else if( $subcommand === "del" ) {
$this->deleteTag( $tag );
}
return;
}
elseif( $command === "photograph" && $subcommand === "accept" ) {
$this->updatePhotograph();
}
elseif( $command === "citation" && $subcommand === "accept" ) {
$this->updateCitation();
}
elseif( $command === "scan" && $subcommand === "accept" ) {
$this->scan();
$prg = false;
}
if( $command === "get" ) {
if( $subcommand === "instruction" ) {
echo $this->getInstruction( $seq );
}
}
if( $subcommand !== "title" &&
$subcommand !== "xml" &&
$command !== "scan" ) {
global $BASE_RECIPE;
$title = $this->getTitle();
if( $this->redirect( $BASE_RECIPE, $this->getId(), $title ) ) {
return;
}
}
if( $command !== "update" && $command !== "get" ) {
if( $command === "view" && $subcommand === "xml" ) {
$this->sendHttpHeaders( "application/xml" );
echo $this->getXml();
}
else {
$this->render( $prg );
}
}
}
}