[dev] [commit] r925 - patches phplib/models templates/struct wwwbase/js wwwbase/struct
automailer at dexonline.ro
automailer at dexonline.ro
Mon Aug 12 17:11:46 EEST 2013
Author: cata
Date: Mon Aug 12 17:11:45 2013
New Revision: 925
Log:
Add proper validation to the meaning editor. Now when the validation fails,
the information correctly propagates across requests.
Added:
patches/00090.sql
Modified:
phplib/models/BaseObject.php
phplib/models/Lexem.php
phplib/models/Meaning.php
templates/struct/dexEdit.ihtml
templates/struct/index.ihtml
wwwbase/js/struct.js
wwwbase/struct/dexEdit.php
wwwbase/struct/index.php
Added: patches/00090.sql
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ patches/00090.sql Mon Aug 12 17:11:45 2013 (r925)
@@ -0,0 +1,2 @@
+alter table Lexem change pronounciations pronunciations varchar(255);
+alter table Lexem change variantOf variantOfId int;
Modified: phplib/models/BaseObject.php
==============================================================================
--- phplib/models/BaseObject.php Fri Aug 9 19:04:33 2013 (r924)
+++ phplib/models/BaseObject.php Mon Aug 12 17:11:45 2013 (r925)
@@ -16,6 +16,15 @@
die("BaseObject::__callStatic() cannot handle method '$name'");
}
+ /* Loads a collection of objects with the given ids, preserving the order. */
+ static function loadByIds($ids) {
+ $results = array();
+ foreach ($ids as $id) {
+ $results[] = Model::factory(get_called_class())->where('id', $id)->find_one();
+ }
+ return $results;
+ }
+
function save() {
/* Auto-save the createDate and modDate fields if the model has them */
if ($this instanceof DatedObject) {
Modified: phplib/models/Lexem.php
==============================================================================
--- phplib/models/Lexem.php Fri Aug 9 19:04:33 2013 (r924)
+++ phplib/models/Lexem.php Mon Aug 12 17:11:45 2013 (r925)
@@ -148,7 +148,7 @@
}
public function getVariantIds() {
- $variants = Model::factory('Lexem')->select('id')->where('variantOf', $this->id)->find_many();
+ $variants = Model::factory('Lexem')->select('id')->where('variantOfId', $this->id)->find_many();
$ids = array();
foreach ($variants as $variant) {
$ids[] = $variant->id;
@@ -329,13 +329,13 @@
$errorCount++;
}
// (2) they are not already variants of another lexem
- if ($variant->variantOf && $variant->variantOf != $this->id) {
- $other = Lexem::get_by_id($variant->variantOf);
+ if ($variant->variantOfId && $variant->variantOfId != $this->id) {
+ $other = Lexem::get_by_id($variant->variantOfId);
FlashMessage::add("\"{$variant}\" este deja marcat ca variantă a lui \"{$other}\".");
$errorCount++;
}
// (3) they do not have their own variants and
- $variantVariantCount = Model::factory('Lexem')->where('variantOf', $variant->id)->count();
+ $variantVariantCount = Model::factory('Lexem')->where('variantOfId', $variant->id)->count();
if ($variantVariantCount) {
FlashMessage::add("\"{$variant}\" are deja propriile lui variante.");
$errorCount++;
@@ -348,19 +348,19 @@
}
if (!$errorCount) {
- $variant->variantOf = $this->id;
+ $variant->variantOfId = $this->id;
$variant->save();
}
}
// Delete variants no longer in the list
if ($variantIds) {
- $lexemsToClear = Model::factory('Lexem')->where('variantOf', $this->id)->where_not_in('id', $variantIds)->find_many();
+ $lexemsToClear = Model::factory('Lexem')->where('variantOfId', $this->id)->where_not_in('id', $variantIds)->find_many();
} else {
- $lexemsToClear = Lexem::get_all_by_variantOf($this->id);
+ $lexemsToClear = Lexem::get_all_by_variantOfId($this->id);
}
foreach($lexemsToClear as $l) {
- $l->variantOf = null;
+ $l->variantOfId = null;
$l->save();
}
}
@@ -430,10 +430,10 @@
InflectedForm::deleteByLexemId($this->id);
Meaning::deleteByLexemId($this->id);
}
- // Clear the variantOf field for lexems having $this as main.
- $lexemsToClear = Lexem::get_all_by_variantOf($this->id);
+ // Clear the variantOfId field for lexems having $this as main.
+ $lexemsToClear = Lexem::get_all_by_variantOfId($this->id);
foreach ($lexemsToClear as $l) {
- $l->variantOf = null;
+ $l->variantOfId = null;
$l->save();
}
parent::delete();
Modified: phplib/models/Meaning.php
==============================================================================
--- phplib/models/Meaning.php Fri Aug 9 19:04:33 2013 (r924)
+++ phplib/models/Meaning.php Mon Aug 12 17:11:45 2013 (r925)
@@ -33,9 +33,11 @@
return $results;
}
- /** Returns a dictionary containing:
+ /**
+ * Returns a dictionary containing:
* 'meaning': a Meaning object
- * 'children': a recusrive dictionary containing this meaning's children
+ * 'sources', 'tags', 'synonyms', 'antonyms': collections of objects related to the meaning
+ * 'children': a recursive dictionary containing this meaning's children
**/
private static function buildTree(&$map, $meaningId, &$children) {
$results = array('meaning' => $map[$meaningId],
@@ -49,6 +51,39 @@
}
return $results;
}
+
+ /**
+ * Convert a tree produced by the tree editor to the format used by loadTree.
+ * We need this in case validation fails and we cannot save the tree, so we need to display it again.
+ **/
+ static function convertTree($meanings) {
+ $meaningStack = array();
+ $results = array();
+ foreach ($meanings as $tuple) {
+ $row = array();
+ $m = $tuple->id ? self::get_by_id($tuple->id) : Model::factory('Meaning')->create();
+ $m->internalRep = $tuple->internalRep;
+ $m->htmlRep = AdminStringUtil::htmlize($m->internalRep, 0);
+ $m->internalComment = $tuple->internalComment;
+ $m->htmlComment = AdminStringUtil::htmlize($m->internalComment, 0);
+ $row['meaning'] = $m;
+
+ $row['sources'] = Source::loadByIds(StringUtil::explode(',', $tuple->sourceIds));
+ $row['tags'] = MeaningTag::loadByIds(StringUtil::explode(',', $tuple->meaningTagIds));
+ $row['synonyms'] = Lexem::loadByIds(StringUtil::explode(',', $tuple->synonymIds));
+ $row['antonyms'] = Lexem::loadByIds(StringUtil::explode(',', $tuple->antonymIds));
+ $row['children'] = array();
+
+ if ($tuple->level) {
+ $meaningStack[$tuple->level - 1]['children'][] = &$row;
+ } else {
+ $results[] = &$row;
+ }
+ $meaningStack[$tuple->level] = &$row;
+ unset($row);
+ }
+ return $results;
+ }
/* Save a tree produced by the tree editor in dexEdit.php */
static function saveTree($meanings, $lexem) {
Modified: templates/struct/dexEdit.ihtml
==============================================================================
--- templates/struct/dexEdit.ihtml Fri Aug 9 19:04:33 2013 (r924)
+++ templates/struct/dexEdit.ihtml Mon Aug 12 17:11:45 2013 (r925)
@@ -30,7 +30,10 @@
<div id="saveButtonContainer">
<input id="dexEditSaveButton" type="button" value="salvează totul"/>
- <div><a href="{$wwwRoot}admin/lexemEdit.php?lexemId={$lexem->id}">editează lexemul</a></div>
+ <div>
+ <a href="?lexemId={$lexem->id}">anulează</a> |
+ <a href="{$wwwRoot}admin/lexemEdit.php?lexemId={$lexem->id}">editează lexemul</a>
+ </div>
</div>
<div id="parameters" class="box">
@@ -42,91 +45,85 @@
title="una sau mai multe silabisiri, despărțite prin virgule"> </span>
<br/>
- <label for="pronounciations">pronunții:</label>
- <input id="pronounciations" name="pronounciations" type="text" value="{$lexem->pronounciations}" size="25"/>
+ <label for="pronunciations">pronunții:</label>
+ <input id="pronunciations" name="pronunciations" type="text" value="{$lexem->pronunciations}" size="25"/>
<span class="tooltip"
title="una sau mai multe pronunții, despărțite prin virgule"> </span>
<br/>
- {if $variantOf}
- <label>variantă a lui:</label>
- <a href="dexEdit.php?lexemId={$variantOf->id}">{$variantOf}</a>
- <span class="tooltip"
- title="Acest lexem este variantă a altuia și nu poate avea sensuri, exemple sau etimologii proprii."> </span>
- {else}
- <label for="variantIds">variante:</label>
- <input id="variantIds" name="variantIds" value="{','|implode:$variantIds}" type="text"/>
- <span class="tooltip"
- title="Variantele nu pot avea sensuri, exemple sau etimologii proprii. Ele pot avea pronunții și silabisiri proprii."> </span>
- <br/>
- {/if}
+ <label>variantă a lui:</label>
+ <input id="variantOfId" name="variantOfId" value="{$lexem->variantOfId}" type="text"/>
+ <span class="tooltip"
+ title="Variantele nu pot avea sensuri, exemple, variante sau etimologii proprii. Ele pot avea pronunții și silabisiri proprii."> </span>
+ <br/>
+
+ <label for="variantIds">variante:</label>
+ <input id="variantIds" name="variantIds" value="{','|implode:$variantIds}" type="text"/>
+ <span class="tooltip"
+ title="Variantele nu pot avea sensuri, exemple, variante sau etimologii proprii. Ele pot avea pronunții și silabisiri proprii."> </span>
</div>
</div>
<div style="clear: both"></div>
- {if !$variantOf}
- <div class="box">
- <div class="boxTitle">Sensuri pentru {include file="common/bits/lexemName.ihtml"}</div>
- <div id="meaningTreeContainer" class="boxContents">
- {include file="common/bits/meaningTree.ihtml" meanings=$meanings id="meaningTree"}
-
- <div id="meaningMenu">
- <input type="button" id="addMeaningButton" value="adaugă sens"/>
- <input type="button" id="addSubmeaningButton" value="adaugă subsens"/>
- <input type="button" id="deleteMeaningButton" value="șterge sens"/>
- </div>
+ <div class="box">
+ <div class="boxTitle">Sensuri pentru {include file="common/bits/lexemName.ihtml"}</div>
+ <div id="meaningTreeContainer" class="boxContents">
+ {include file="common/bits/meaningTree.ihtml" meanings=$meanings id="meaningTree"}
+
+ <div id="meaningMenu">
+ <input type="button" id="addMeaningButton" value="adaugă sens"/>
+ <input type="button" id="addSubmeaningButton" value="adaugă subsens"/>
+ <input type="button" id="deleteMeaningButton" value="șterge sens"/>
</div>
</div>
- {/if}
+ </div>
+
+ <div id="dexEditLeftColumn">
+ <div id="meaningEditor" class="box">
+ <div class="boxTitle">Editorul de sensuri</div>
+ <div class="boxContents">
+ <textarea id="editorInternalRep" rows="10" cols="10" disabled="disabled" placeholder="sensul definiției..."></textarea><br/>
+ <textarea id="editorInternalComment" rows="3" cols="10" disabled="disabled" placeholder="comentariu..."></textarea><br/>
- {if !$variantOf}
- <div id="dexEditLeftColumn">
- <div id="meaningEditor" class="box">
- <div class="boxTitle">Editorul de sensuri</div>
- <div class="boxContents">
- <textarea id="editorInternalRep" rows="10" cols="10" disabled="disabled" placeholder="sensul definiției..."></textarea><br/>
- <textarea id="editorInternalComment" rows="3" cols="10" disabled="disabled" placeholder="comentariu..."></textarea><br/>
-
- <div class="editorLabel">surse:</div>
- <div class="editorFieldContainer">
- <select id="editorSources" multiple="multiple">
- {foreach from=$sources item=s}
- <option value="{$s->id}">{$s->shortName}</option>
- {/foreach}
- </select>
- </div>
- <div style="clear: both"></div>
-
- <div class="editorLabel">etichete:</div>
- <div class="editorFieldContainer">
- <select id="editorTags" multiple="multiple">
- {foreach from=$meaningTags item=mt}
- <option value="{$mt->id}">{$mt->value}</option>
- {/foreach}
- </select>
- </div>
- <div style="clear: both"></div>
-
- <div class="editorLabel">sinonime:</div>
- <div class="editorFieldContainer">
- <input id="editorSynonyms" type="hidden"/>
- </div>
- <div style="clear: both"></div>
-
- <div class="editorLabel">antonime:</div>
- <div class="editorFieldContainer">
- <input id="editorAntonyms" type="hidden"/>
- </div>
- <div style="clear: both"></div>
-
- <input id="editMeaningAcceptButton" type="button" disabled="disabled" value="acceptă"/>
- <input id="editMeaningCancelButton" type="button" disabled="disabled" value="renunță"/>
+ <div class="editorLabel">surse:</div>
+ <div class="editorFieldContainer">
+ <select id="editorSources" multiple="multiple">
+ {foreach from=$sources item=s}
+ <option value="{$s->id}">{$s->shortName}</option>
+ {/foreach}
+ </select>
</div>
+ <div style="clear: both"></div>
+
+ <div class="editorLabel">etichete:</div>
+ <div class="editorFieldContainer">
+ <select id="editorTags" multiple="multiple">
+ {foreach from=$meaningTags item=mt}
+ <option value="{$mt->id}">{$mt->value}</option>
+ {/foreach}
+ </select>
+ </div>
+ <div style="clear: both"></div>
+
+ <div class="editorLabel">sinonime:</div>
+ <div class="editorFieldContainer">
+ <input id="editorSynonyms" type="hidden"/>
+ </div>
+ <div style="clear: both"></div>
+
+ <div class="editorLabel">antonime:</div>
+ <div class="editorFieldContainer">
+ <input id="editorAntonyms" type="hidden"/>
+ </div>
+ <div style="clear: both"></div>
+
+ <input id="editMeaningAcceptButton" type="button" disabled="disabled" value="acceptă"/>
+ <input id="editMeaningCancelButton" type="button" disabled="disabled" value="renunță"/>
</div>
</div>
- {/if}
+ </div>
- <div id="definitionBox" class="{if !$variantOf}dexEditRightColumn{/if} box">
+ <div id="definitionBox" class="dexEditRightColumn box">
<div class="boxTitle">Definiții pentru {include file="common/bits/lexemName.ihtml"}</div>
<div class="boxContents">
{foreach from=$searchResults item=row}
Modified: templates/struct/index.ihtml
==============================================================================
--- templates/struct/index.ihtml Fri Aug 9 19:04:33 2013 (r924)
+++ templates/struct/index.ihtml Mon Aug 12 17:11:45 2013 (r925)
@@ -1,6 +1,6 @@
<h3>Alege un cuvânt pentru structurare</h3>
-<form id="meaningForm" action="dexEdit.php" method="post">
+<form id="meaningForm" action="dexEdit.php" method="get">
<ul>
<li>
@@ -12,7 +12,7 @@
<li>
la alegere:
<input id="structLexemFinder" type="text" name="lexemId"/>
- <input type="submit" name="submitButton" value="Caută"/>
+ <button type="button" onclick="$('#meaningForm').submit();">Caută</button>
</li>
</ul>
Modified: wwwbase/js/struct.js
==============================================================================
--- wwwbase/js/struct.js Fri Aug 9 19:04:33 2013 (r924)
+++ wwwbase/js/struct.js Mon Aug 12 17:11:45 2013 (r925)
@@ -23,13 +23,13 @@
placeholder: 'adaugă o sursă...',
width: '315px',
});
- $('#editorSources').select2('disable');
+ $('#editorSources').select2('enable', false);
$('#editorTags').select2({
placeholder: 'adaugă o etichetă...',
width: '315px',
});
- $('#editorTags').select2('disable');
+ $('#editorTags').select2('enable', false);
$('#editorSynonyms').select2({
ajax: struct_lexemAjax,
@@ -39,7 +39,7 @@
placeholder: 'adaugă un sinonim...',
width: '315px',
});
- $('#editorSynonyms').select2('disable');
+ $('#editorSynonyms').select2('enable', false);
$('#editorAntonyms').select2({
ajax: struct_lexemAjax,
@@ -49,7 +49,7 @@
placeholder: 'adaugă un antonim...',
width: '315px',
});
- $('#editorAntonyms').select2('disable');
+ $('#editorAntonyms').select2('enable', false);
$('#editorInternalRep, #editorInternalComment, #editorSources, #editorTags, #editorSynonyms, #editorAntonyms').bind(
'change keyup input paste', function() { struct_anyChanges = true; });
@@ -62,6 +62,15 @@
width: '217px',
});
+ $('#variantOfId').select2({
+ ajax: struct_lexemAjax,
+ allowClear: true,
+ initSelection: select2InitSelectionAjaxSingle,
+ minimumInputLength: 1,
+ placeholder: 'alegeți un lexem (opțional)',
+ width: '217px',
+ });
+
$('#editMeaningAcceptButton').click(acceptMeaningEdit);
$('#editMeaningCancelButton').click(endMeaningEdit);
$('#dexEditSaveButton').click(dexEditSaveEverything);
@@ -77,6 +86,7 @@
ajax: struct_lexemAjax,
minimumInputLength: 1,
placeholder: 'caută un lexem...',
+ width: '300px',
});
}
@@ -142,6 +152,16 @@
callback(data);
}
+function select2InitSelectionAjaxSingle(element, callback) {
+ var id = $(element).val();
+ if (id) {
+ $.ajax(wwwRoot + 'ajax/getLexemById.php?id=' + id, {dataType: 'json'})
+ .done(function(data) {
+ callback({ id: id, text: data });
+ });
+ }
+}
+
function select2InitSelectionAjax(element, callback) {
var data = [];
@@ -309,13 +329,13 @@
$('#editorInternalRep').val('');
$('#editorInternalComment').val('');
$('#editorSources').select2('val', []);
- $('#editorSources').select2('disable');
+ $('#editorSources').select2('enable', false);
$('#editorTags').select2('val', []);
- $('#editorTags').select2('disable');
+ $('#editorTags').select2('enable', false);
$('#editorSynonyms').select2('data', []);
- $('#editorSynonyms').select2('disable');
+ $('#editorSynonyms').select2('enable', false);
$('#editorAntonyms').select2('data', []);
- $('#editorAntonyms').select2('disable');
+ $('#editorAntonyms').select2('enable', false);
}
// Iterate a meaning tree node recursively and collect meaning-related fields
Modified: wwwbase/struct/dexEdit.php
==============================================================================
--- wwwbase/struct/dexEdit.php Fri Aug 9 19:04:33 2013 (r924)
+++ wwwbase/struct/dexEdit.php Mon Aug 12 17:11:45 2013 (r925)
@@ -1,39 +1,39 @@
-<?php
+<?Php
require_once("../../phplib/util.php");
util_assertModerator(PRIV_EDIT);
util_assertNotMirror();
$lexemId = util_getRequestIntParameter('lexemId');
$hyphenations = util_getRequestParameter('hyphenations');
-$pronounciations = util_getRequestParameter('pronounciations');
-$variantIds = util_getRequestParameter('variantIds');
+$pronunciations = util_getRequestParameter('pronunciations');
+$variantIds = util_getRequestCsv('variantIds');
+$variantOfId = util_getRequestParameter('variantOfId');
$jsonMeanings = util_getRequestParameter('jsonMeanings');
$lexem = Lexem::get_by_id($lexemId);
-$mainVariant = Lexem::get_by_id($lexem->variantOf);
if ($jsonMeanings) {
- $meanings = json_decode($jsonMeanings);
- if ($mainVariant && !empty($meanings)) {
- FlashMessage::add("Acest lexem este o variantă a lui {$mainVariant} și nu poate avea el însuși sensuri.");
- } else {
- Meaning::saveTree($meanings, $lexem);
- }
-
$lexem->hyphenations = $hyphenations;
- $lexem->pronounciations = $pronounciations;
- $lexem->save();
+ $lexem->pronunciations = $pronunciations;
+ $lexem->variantOfId = $variantOfId ? $variantOfId : null;
+ $variantOf = Lexem::get_by_id($lexem->variantOfId);
+ $meanings = json_decode($jsonMeanings);
- // TODO: Add a validation routine that checks everything before saving anything
- // Save variants, but only if they meet certain criteria
- $variantIds = StringUtil::explode(',', $variantIds);
- if ($mainVariant && !empty($variantIds)) {
- FlashMessage::add("Acest lexem este o variantă a lui {$mainVariant} și nu poate avea el însuși variante.");
- } else {
+ if (validate($lexem, $variantOf, $variantIds, $meanings)) {
+ // Case 1: Validation passed
+ Meaning::saveTree($meanings, $lexem);
+ $lexem->save();
$lexem->updateVariants($variantIds);
+ util_redirect("dexEdit.php?lexemId={$lexem->id}");
+ } else {
+ // Case 2: Validation failed
+ SmartyWrap::assign('variantIds', $variantIds);
+ SmartyWrap::assign('meanings', Meaning::convertTree($meanings));
}
-
- util_redirect("dexEdit.php?lexemId={$lexem->id}");
+} else {
+ // Case 3: First time loading this page
+ SmartyWrap::assign('variantIds', $lexem->getVariantIds());
+ SmartyWrap::assign('meanings', Meaning::loadTree($lexem->id));
}
$defs = Definition::loadByLexemId($lexem->id);
@@ -44,14 +44,23 @@
$meaningTags = Model::factory('MeaningTag')->order_by_asc('value')->find_many();
SmartyWrap::assign('lexem', $lexem);
-SmartyWrap::assign('meanings', Meaning::loadTree($lexem->id));
SmartyWrap::assign('meaningTags', $meaningTags);
SmartyWrap::assign('searchResults', $searchResults);
-SmartyWrap::assign('variantOf', $mainVariant);
-SmartyWrap::assign('variantIds', $lexem->getVariantIds());
SmartyWrap::assign('pageTitle', "Editare lexem: {$lexem->formNoAccent}");
SmartyWrap::addCss('jqueryui', 'easyui', 'select2', 'struct', 'flex');
SmartyWrap::addJs('dex', 'jquery', 'easyui', 'jqueryui', 'select2', 'struct');
SmartyWrap::displayWithoutSkin('struct/dexEdit.ihtml');
+/**************************************************************************/
+
+function validate($lexem, $variantOf, $variantIds, $meanings) {
+ if ($variantOf && !empty($meanings)) {
+ FlashMessage::add("Acest lexem este o variantă a lui {$variantOf} și nu poate avea el însuși sensuri.");
+ }
+ if ($variantOf && !empty($variantIds)) {
+ FlashMessage::add("Acest lexem este o variantă a lui {$variantOf} și nu poate avea el însuși variante.");
+ }
+ return FlashMessage::getMessage() == null;
+}
+
?>
Modified: wwwbase/struct/index.php
==============================================================================
--- wwwbase/struct/index.php Fri Aug 9 19:04:33 2013 (r924)
+++ wwwbase/struct/index.php Mon Aug 12 17:11:45 2013 (r925)
@@ -1,12 +1,26 @@
<?php
require_once("../../phplib/util.php");
+// Select the first 500 lexems with short definitions and present 20 of them at random.
+// It's hard to select all the easy lexems because the query is very slow.
+define('NUM_EASY_LEXEMS', 500);
+define('NUM_EASY_LEXEMS_SHOWN', 20);
+
$easyLexems = Model::factory('Lexem')
->raw_query('select l.* from Lexem l, LexemDefinitionMap ldm, Definition d ' .
'where l.id = ldm.lexemId and ldm.definitionId = d.id and d.status = 0 ' .
- 'group by l.id having sum(char_length(internalRep)) <= 100 limit 10')
+ 'group by l.id having sum(char_length(internalRep)) <= 100 limit ' . NUM_EASY_LEXEMS)
->find_many();
+// Now select NUM_EASY_LEXEMS_SHOWN of them at random
+for ($i = 0; $i < NUM_EASY_LEXEMS_SHOWN; $i++) {
+ $j = rand($i, NUM_EASY_LEXEMS - 1);
+ $tmp = $easyLexems[$i];
+ $easyLexems[$i] = $easyLexems[$j];
+ $easyLexems[$j] = $tmp;
+}
+array_splice($easyLexems, NUM_EASY_LEXEMS_SHOWN);
+
SmartyWrap::assign('easyLexems', $easyLexems);
SmartyWrap::assign('sectionTitle', 'Structurare definiții');
SmartyWrap::addCss('select2');
More information about the Dev
mailing list