7 define(
"ILIAS_LANGUAGE_MODULE",
"Services/Language");
9 require_once(
"./Services/Object/classes/class.ilObjectGUI.php");
10 require_once(
"Services/Language/classes/class.ilObjLanguageAccess.php");
40 public function __construct($a_data, $a_id = 0, $a_call_by_reference =
false)
45 $lng->loadLanguageModule(
"administration");
46 $lng->loadLanguageModule(
"meta");
49 $ilCtrl->saveParameter($this,
"view_mode");
53 if (!$this->
id =
$_GET[
'obj_id']) {
58 parent::__construct($a_data, $this->
id,
false,
true);
61 if (!is_array(
$_SESSION[
'lang_ext_maintenance'])) {
64 $this->session =&
$_SESSION[
'lang_ext_maintenance'];
68 $this->langmode = $ilClientIniFile->readVariable(
"system",
"LANGMODE");
80 require_once(
"Services/Language/classes/class.ilObjLanguageExt.php");
101 $this->ilErr->raiseError($this->lng->txt(
"permission_denied"), $this->ilErr->MESSAGE);
105 $cmd = $this->ctrl->getCmd(
"view") .
"Object";
108 $ilHelp->setScreenIdComponent(
"lng");
128 include_once
'./Services/Language/classes/class.ilLanguageExtTableGUI.php';
130 'langmode' => $this->langmode,
131 'lang_key'=> $this->
object->key,
148 $comments = $this->
object->getAllRemarks();
152 $compare = $table_gui->getFilterItemByPostVar(
'compare')->getValue();
153 if ($compare == $this->
object->key) {
154 $compare_object = $this->
object->getGlobalLanguageFile();
155 $compare_content = $compare_object->getAllValues();
156 $compare_comments = $compare_object->getAllComments();
167 if (
$_GET[
'reset_offset']) {
168 $table_gui->resetOffset();
171 if (!isset($compare_content)) {
189 foreach ($translations as
$name => $translation) {
190 $keys = explode($this->lng->separator,
$name);
191 $db_found[] =
$keys[1];
193 $missing_entries = array_diff($topics, $db_found);
198 $filter_mode = $table_gui->getFilterItemByPostVar(
'mode')->getValue();
199 $filter_pattern = $table_gui->getFilterItemByPostVar(
'pattern')->getValue();
200 $filter_module = $table_gui->getFilterItemByPostVar(
'module')->getValue();
201 $filter_module = $filter_module ==
'all' ?
'' : $filter_module;
202 $filter_modules = $filter_module ?
array($filter_module) :
array();
203 $filter_identifier = $table_gui->getFilterItemByPostVar(
'identifier')->getValue();
204 $filter_topics = $filter_identifier ?
array($filter_identifier) :
array();
206 if (!isset($compare_content)) {
216 switch ($filter_mode) {
218 $translations = $this->
object->getChangedValues(
226 $translations = $this->
object->getAddedValues(
234 $translations = $this->
object->getUnchangedValues(
242 $translations = $this->
object->getCommentedValues(
250 $translations = $this->
object->getAllValues(
256 $translations = array_intersect_key($translations, $remarks);
260 $translations = $this->
object->getAllValues(
266 $translations = array_intersect_assoc($translations, $compare_content);
270 $translations = $this->
object->getAllValues(
276 $translations = array_diff_assoc($translations, $compare_content);
280 $former_file = $this->
object->getDataPath() .
'/ilias_' . $this->
object->key .
'.lang';
281 if (!is_readable($former_file)) {
283 .
'<br />' . $this->lng->txt(
"language_former_file_description"),
false);
284 $translations =
array();
287 $global_file_obj = $this->
object->getGlobalLanguageFile();
289 $former_file_obj->read();
290 $global_changes = array_diff_assoc(
291 $global_file_obj->getAllValues(),
292 $former_file_obj->getAllValues()
294 if (!count($global_changes)) {
295 ilUtil::sendInfo(sprintf($this->lng->txt(
"language_former_file_equal"), $former_file)
296 .
'<br />' . $this->lng->txt(
"language_former_file_description"),
false);
297 $translations =
array();
300 $translations = $this->
object->getChangedValues(
306 $translations = array_intersect_key($translations, $global_changes);
311 $translations = $this->
object->getAllValues(
321 foreach ($translations as
$name => $translation) {
322 $keys = explode($this->lng->separator,
$name);
328 $row[
"translation"] = $translation;
330 $row[
"default"] = $compare_content[
$name];
331 $row[
"default_comment"] = $compare_comments[
$name];
337 $table_gui->setData(
$data);
338 $tpl->setContent($table_gui->getHTML() .
348 $table_gui->writeFilterToSession();
349 $table_gui->resetOffset();
359 $table_gui->resetOffset();
360 $table_gui->resetFilter();
370 $save_array =
array();
371 $remarks_array =
array();
375 $key = str_replace(
'_POSTDOT_',
'.',
$key);
376 $key = str_replace(
'_POSTSPACE_',
' ',
$key);
382 if (count(
$keys) == 2) {
384 $value = preg_replace(
"/(\015\012)|(\015)|(\012)/",
"<br />", $value);
386 $save_array[
$key] = $value;
389 $remarks_array[
$key] =
$_POST[
$key . $this->lng->separator .
"comment"];
406 require_once(
"./Services/Form/classes/class.ilPropertyFormGUI.php");
408 $form->setFormAction($this->ctrl->getFormAction($this));
409 $form->setTitle($this->lng->txt(
"language_import_file"));
410 $form->addCommandButton(
'upload', $this->lng->txt(
"upload"));
416 $ro =
new ilRadioOption($this->lng->txt(
"language_mode_existing_keepall"),
"keepall");
417 $ro->
setInfo($this->lng->txt(
"language_mode_existing_keepall_info"));
419 $ro =
new ilRadioOption($this->lng->txt(
"language_mode_existing_keepnew"),
"keepnew");
420 $ro->
setInfo($this->lng->txt(
"language_mode_existing_keepnew_info"));
422 $ro =
new ilRadioOption($this->lng->txt(
"language_mode_existing_replace"),
"replace");
423 $ro->
setInfo($this->lng->txt(
"language_mode_existing_replace_info"));
425 $ro =
new ilRadioOption($this->lng->txt(
"language_mode_existing_delete"),
"delete");
426 $ro->
setInfo($this->lng->txt(
"language_mode_existing_delete_info"));
428 $rg->setValue($this->session[
"import"][
"mode_existing"] ? $this->session[
"import"][
"mode_existing"] :
"keepall");
431 $this->tpl->setContent(
$form->getHTML());
446 $upload = $DIC->upload();
449 if (!$upload->hasUploads()) {
450 throw new ilException($DIC->language()->txt(
"upload_error_file_not_found"));
452 $UploadResult = $upload->getResults()[$_FILES[
'userfile'][
'tmp_name']];
454 $ProcessingStatus = $UploadResult->getStatus();
455 if ($ProcessingStatus->getCode() === ProcessingStatus::REJECTED) {
456 throw new ilException($ProcessingStatus->getMessage());
461 $upload->moveOneFileTo($UploadResult,
'', Location::TEMPORARY, basename($tempfile),
true);
462 $this->
object->importLanguageFile($tempfile,
$_POST[
'mode_existing']);
464 $tempfs = $DIC->filesystem()->temp();
465 $tempfs->delete(basename($tempfile));
468 $this->ctrl->redirect($this,
'import');
471 ilUtil::sendSuccess(sprintf($this->lng->txt(
"language_file_imported"), $_FILES[
'userfile'][
'name']),
true);
472 $this->ctrl->redirect($this,
'import');
481 require_once(
"./Services/Form/classes/class.ilPropertyFormGUI.php");
483 $form->setFormAction($this->ctrl->getFormAction($this));
484 $form->setTitle($this->lng->txt(
"language_export_file"));
485 $form->setPreventDoubleSubmission(
false);
486 $form->addCommandButton(
'download', $this->lng->txt(
"download"));
489 $ro =
new ilRadioOption($this->lng->txt(
"language_scope_global"),
"global");
490 $ro->
setInfo($this->lng->txt(
"language_scope_global_info"));
492 $ro =
new ilRadioOption($this->lng->txt(
"language_scope_local"),
"local");
493 $ro->
setInfo($this->lng->txt(
"language_scope_local_info"));
495 if ($this->langmode) {
496 $ro =
new ilRadioOption($this->lng->txt(
"language_scope_added"),
"added");
497 $ro->
setInfo($this->lng->txt(
"language_scope_added_info"));
500 $ro =
new ilRadioOption($this->lng->txt(
"language_scope_unchanged"),
"unchanged");
501 $ro->
setInfo($this->lng->txt(
"language_scope_unchanged_info"));
503 if ($this->langmode) {
504 $ro =
new ilRadioOption($this->lng->txt(
"language_scope_merged"),
"merged");
505 $ro->
setInfo($this->lng->txt(
"language_scope_merged_info"));
509 $rg->setValue($this->session[
"export"][
"scope"] ? $this->session[
"export"][
"scope"] :
"global");
512 $this->tpl->setContent(
$form->getHTML());
524 $filename =
'ilias_' . $this->
object->key .
'_' 526 .
"-" .
date(
'Y-m-d')
527 .
".lang." . $this->session[
"export"][
"scope"];
529 $global_file_obj = $this->
object->getGlobalLanguageFile();
532 if (
$_POST[
"scope"] ==
'global') {
533 $local_file_obj->setParam(
"author", $global_file_obj->getParam(
'author'));
534 $local_file_obj->setParam(
"version", $global_file_obj->getParam(
'version'));
535 $local_file_obj->setAllValues($this->
object->getAllValues());
536 if ($this->langmode) {
537 $local_file_obj->setAllComments($this->
object->getAllRemarks());
539 } elseif (
$_POST[
"scope"] ==
'local') {
540 $local_file_obj->setParam(
"based_on", $global_file_obj->getParam(
'version'));
541 $local_file_obj->setAllValues($this->
object->getChangedValues());
542 if ($this->langmode) {
543 $local_file_obj->setAllComments($this->
object->getAllRemarks());
545 } elseif (
$_POST[
"scope"] ==
'added') {
546 $local_file_obj->setParam(
"author", $global_file_obj->getParam(
'author'));
547 $local_file_obj->setParam(
"version", $global_file_obj->getParam(
'version'));
548 $local_file_obj->setAllValues($this->
object->getAddedValues());
549 $local_file_obj->setAllComments($this->
object->getAllRemarks());
550 } elseif (
$_POST[
"scope"] ==
'unchanged') {
551 $local_file_obj->setParam(
"author", $global_file_obj->getParam(
'author'));
552 $local_file_obj->setParam(
"version", $global_file_obj->getParam(
'version'));
553 $local_file_obj->setAllValues($this->
object->getUnchangedValues());
554 if ($this->langmode) {
555 $local_file_obj->setAllComments($this->
object->getAllRemarks());
557 } elseif (
$_POST[
"scope"] ==
'merged') {
558 $local_file_obj->setParam(
"author", $global_file_obj->getParam(
'author'));
559 $local_file_obj->setParam(
"version", $global_file_obj->getParam(
'version'));
560 $local_file_obj->setAllValues($this->
object->getMergedValues());
561 $local_file_obj->setAllComments($this->
object->getMergedRemarks());
573 require_once(
"./Services/Form/classes/class.ilPropertyFormGUI.php");
575 $form->setFormAction($this->ctrl->getFormAction($this));
576 $form->setTitle($this->lng->txt(
"language_maintenance"));
577 $form->setPreventDoubleSubmission(
false);
578 $form->addCommandButton(
'maintainExecute', $this->lng->txt(
"language_process_maintenance"));
581 $ro =
new ilRadioOption($this->lng->txt(
"language_load_local_changes"),
"load");
582 $ro->
setInfo(sprintf($this->lng->txt(
"language_load_local_changes_info"), $this->
object->key));
584 $ro =
new ilRadioOption($this->lng->txt(
"language_clear_local_changes"),
"clear");
585 $ro->
setInfo(sprintf($this->lng->txt(
"language_clear_local_changes_info"), $this->
object->key));
587 if ($this->langmode) {
588 $ro =
new ilRadioOption($this->lng->txt(
"language_delete_local_additions"),
"delete_added");
589 $ro->
setInfo(sprintf($this->lng->txt(
"language_delete_local_additions_info"), $this->
object->key));
591 $ro =
new ilRadioOption($this->lng->txt(
"language_remove_local_file"),
"remove_local_file");
592 $ro->
setInfo(sprintf($this->lng->txt(
"language_remove_local_file_info"), $this->
object->key));
594 $ro =
new ilRadioOption($this->lng->txt(
"language_merge_local_changes"),
"merge");
595 $ro->
setInfo(sprintf($this->lng->txt(
"language_merge_local_changes_info"), $this->
object->key));
598 $ro =
new ilRadioOption($this->lng->txt(
"language_save_dist"),
"save_dist");
599 $ro->
setInfo(sprintf($this->lng->txt(
"language_save_dist_info"), $this->
object->key));
601 $rg->setValue($this->session[
"maintain"]);
604 $this->tpl->setContent(
$form->getHTML());
610 if (isset(
$_POST[
"maintain"])) {
614 switch (
$_POST[
"maintain"]) {
619 $orig_file = $this->
object->getLangPath() .
'/ilias_' . $this->
object->key .
'.lang';
620 $copy_file = $this->
object->getDataPath() .
'/ilias_' . $this->
object->key .
'.lang';
621 if (@copy($orig_file, $copy_file)) {
630 $lang_file = $this->
object->getCustLangPath() .
'/ilias_' . $this->
object->key .
'.lang.local';
631 if (is_file($lang_file) and is_readable($lang_file)) {
632 $this->
object->importLanguageFile($lang_file,
'replace');
633 $this->
object->setLocal(
true);
642 $lang_file = $this->
object->getLangPath() .
'/ilias_' . $this->
object->key .
'.lang';
643 if (is_file($lang_file) and is_readable($lang_file)) {
644 $this->
object->importLanguageFile($lang_file,
'delete');
645 $this->
object->setLocal(
false);
660 $orig_file = $this->
object->getLangPath() .
'/ilias_' . $this->
object->key .
'.lang';
661 $copy_file = $this->
object->getCustLangPath() .
'/ilias_' . $this->
object->key .
'.lang';
663 if (is_file($orig_file) and is_writable($orig_file)) {
665 @copy($orig_file, $copy_file);
668 $global_file_obj = $this->
object->getGlobalLanguageFile();
669 $global_file_obj->setAllValues($this->
object->getMergedValues());
670 $global_file_obj->setAllComments($this->
object->getMergedRemarks());
671 $global_file_obj->write();
679 case "remove_local_file":
680 $lang_file = $this->
object->getCustLangPath() .
'/ilias_' . $this->
object->key .
'.lang.local';
682 if (!is_file($lang_file)) {
683 $this->
object->setLocal(
false);
685 } elseif (@unlink($lang_file)) {
686 $this->
object->setLocal(
false);
694 $this->ctrl->redirect($this,
"maintain");
704 $translate_key =
"lang_translate_" . $this->
object->key;
708 $ilSetting->set($translate_key, (
bool)
$_POST[
"translation"]);
711 $translate = $ilSetting->get($translate_key,
false);
713 require_once(
"./Services/Form/classes/class.ilPropertyFormGUI.php");
715 $form->setFormAction($this->ctrl->getFormAction($this));
716 $form->setTitle($this->lng->txt(
"language_settings"));
717 $form->setPreventDoubleSubmission(
false);
718 $form->addCommandButton(
'settings', $this->lng->txt(
"language_change_settings"));
720 $ci =
new ilCheckboxInputGUI($this->lng->txt(
"language_translation_enabled"),
"translation");
722 $ci->setInfo($this->lng->txt(
"language_note_translation"));
725 $this->tpl->setContent(
$form->getHTML());
736 $total =
array(
"module"=>
'',
"all"=>0,
"changed"=>0,
"unchanged"=>0);
737 foreach ($modules as
$module) {
741 $row[
'changed'] = count($this->
object->getChangedValues(
array($module)));
742 $row[
'unchanged'] = $row[
'all']-$row[
'changed'];
743 $total[
'all'] += $row[
'all'];
744 $total[
'changed'] += $row[
'changed'];
745 $total[
'unchanged'] += $row[
'unchanged'];
748 $total[
'module'] =
"<b>" . $this->lng->txt(
"language_all_modules") .
"</b>";
751 $total[
'unchanged'] =
"<b>" .
$total[
'unchanged'] .
"</b>";
755 include_once
'Services/Table/classes/class.ilTable2GUI.php';
757 $table_gui->setRowTemplate(
"tpl.lang_statistics_row.html",
"Services/Language");
758 $table_gui->setEnableTitle(
false);
759 $table_gui->setEnableNumInfo(
false);
760 $table_gui->setLimit(count(
$data));
763 $table_gui->addColumn(ucfirst($this->lng->txt(
"module")),
"",
"25%");
764 $table_gui->addColumn($this->lng->txt(
"language_scope_global"),
"",
"25%");
765 $table_gui->addColumn($this->lng->txt(
"language_scope_local"),
"",
"25%");
766 $table_gui->addColumn($this->lng->txt(
"language_scope_unchanged"),
"",
"25%");
768 $table_gui->setData(
$data);
770 $this->tpl->setContent($table_gui->getHTML());
783 $this->tabs_gui->addTarget(
785 $this->ctrl->getLinkTarget($this,
"view"),
786 array(
"",
"view",
"cancel",
"save")
789 $this->tabs_gui->addTarget(
791 $this->ctrl->getLinkTarget($this,
"export"),
792 array(
"export",
"download")
795 $this->tabs_gui->addTarget(
797 $this->ctrl->getLinkTarget($this,
"import"),
798 array(
"import",
"upload")
801 $this->tabs_gui->addTarget(
803 $this->ctrl->getLinkTarget($this,
"maintain"),
807 $this->tabs_gui->addTarget(
809 $this->ctrl->getLinkTarget($this,
"settings"),
813 $this->tabs_gui->addTarget(
814 "language_statistics",
815 $this->ctrl->getLinkTarget($this,
"statistics"),
831 parent::addAdminLocatorItems(
true);
834 $this->lng->txt(
"languages"),
835 $this->ctrl->getLinkTargetByClass(
"ilobjlanguagefoldergui",
"")
839 $this->lng->txt(
"meta_l_" . $this->object->getTitle()),
840 $this->ctrl->getLinkTarget($this,
"view")
853 $this->tpl->setHeaderPageTitle($this->lng->txt(
"translation"));
854 $this->tpl->setTitle($this->lng->txt(
"translation") .
" " . $this->lng->txt(
"meta_l_" . $this->
object->key));
856 $this->tpl->setTitle($this->lng->txt(
"meta_l_" . $this->object->key));
858 $this->tpl->setTitleIcon(
ilUtil::getImagePath(
"icon_lngf.svg"), $this->lng->txt(
"obj_" . $this->object->getType()));
870 if (!is_array($a_missing) ||
871 !
sizeof($a_missing)) {
875 $res =
array(
'<h3>' . $this->lng->txt(
"adm_missing_entries") .
'</h3>',
'<ul>');
877 foreach ($a_missing as $entry) {
878 $ilCtrl->setParameter($this,
"eid", $entry);
879 $res[] =
'<li>' . $entry .
880 ' <a href="' . $ilCtrl->getLinkTarget($this,
"addNewEntry") .
881 '">' . $this->lng->txt(
"adm_missing_entry_add_action") .
'</a></li>';
882 $ilCtrl->setParameter($this,
"eid",
"");
887 return implode(
"\n",
$res);
900 $tpl->setContent($a_form->getHTML());
913 $ilCtrl->redirect($this,
"view");
916 include_once
"Services/Form/classes/class.ilPropertyFormGUI.php";
918 $form->setFormAction($ilCtrl->getFormAction($this,
"saveNewEntry"));
919 $form->setTitle($this->lng->txt(
"adm_missing_entry_add"));
922 $options = array_combine($mods, $mods);
925 $mod->setOptions(
array(
""=>$this->lng->txt(
"please_select"))+
$options);
926 $mod->setRequired(
true);
927 $form->addItem($mod);
930 $id->setValue($a_id);
931 $id->setDisabled(
true);
934 foreach ($this->lng->getInstalledLanguages() as $lang_key) {
935 $trans =
new ilTextInputGUI($this->lng->txt(
"meta_l_" . $lang_key),
"trans_" . $lang_key);
936 if (in_array($lang_key,
array(
"de",
"en"))) {
939 $form->addItem($trans);
942 $form->addCommandButton(
"saveNewEntry", $this->lng->txt(
"save"));
943 $form->addCommandButton(
"view", $this->lng->txt(
"cancel"));
953 if (
$form->checkInput()) {
954 $mod =
$form->getInput(
"mod");
958 foreach ($this->lng->getInstalledLanguages() as $lang_key) {
959 $trans = trim(
$form->getInput(
"trans_" . $lang_key));
972 $set = $ilDB->query(
"SELECT lang_array FROM lng_modules" .
973 " WHERE lang_key = " . $ilDB->quote($lang_key,
"text") .
974 " AND module = " . $ilDB->quote($mod,
"text"));
975 $row = $ilDB->fetchAssoc($set);
976 $entries = unserialize(
$row[
"lang_array"]);
977 if (is_array($entries)) {
978 $entries[
$id] = $trans;
979 ilObjLanguage::replaceLangModule($lang_key, $mod, $entries);
985 $ilCtrl->redirect($this,
"view");
988 $form->setValuesByPost();
static sendSuccess($a_info="", $a_keep=false)
Send Success Message to Screen.
downloadObject()
Download a language file.
This class represents an option in a radio group.
static deliverData($a_data, $a_filename, $mime="application/octet-stream", $charset="")
deliver data for download via browser.
executeCommand()
execute command
assignObject()
Assign the extended language object.
viewObject()
Show the edit screen.
uploadObject()
Process an uploaded language file.
applyFilterObject()
Apply filter.
static _getRemarks($a_lang_key, $a_all_changed=false)
Get all remarks of a language.
static _isPageTranslation()
Check if the current request is a page translation.
static _getSavedModules()
Get the stored modules from the user session.
getViewTable()
Get the table to view language entries.
addAdminLocatorItems($a_do_not_add_object=false)
Set the locator for admin mode (Overwritten from ilObjectGUI, called by prepareOutput) ...
static _deleteValues($a_lang_key, $a_values=array())
Delete a set of translation in the database.
if(!array_key_exists('StateId', $_REQUEST)) $id
cancelObject()
Cancel the current action.
Class ilObjLanguageExtGUI.
__construct($a_data, $a_id=0, $a_call_by_reference=false)
Constructor.
resetFilterObject()
Reset filter.
exportObject()
Show the screen to export a language file.
static _checkMaintenance()
Permission check for language maintenance (import/export)
static _lookupId($a_key)
Lookup the object ID for a language key.
if($modEnd===false) $module
initAddNewEntryForm($a_id=null)
static sendInfo($a_info="", $a_keep=false)
Send Info Message to Screen.
importObject()
Show the screen to import a language file.
foreach($_POST as $key=> $value) $res
if(isset($_POST['submit'])) $form
static getImagePath($img, $module_path="", $mode="output", $offline=false)
get image path (for images located in a template directory)
Class ilObjectGUI Basic methods of all Output classes.
static _getValues( $a_lang_key, $a_modules=array(), $a_topics=array(), $a_pattern='', $a_state='')
Get the translations of specified topics.
date( 'd-M-Y', $objPHPExcel->getProperties() ->getCreated())
getAdminTabs()
Get tabs for admin mode (Overwritten from ilObjectGUI, called by prepareOutput)
This class represents a text property in a property form.
buildMissingEntries(array $a_missing=null)
statisticsObject()
Print out statistics about the language.
settingsObject()
Set the language settings.
setTitleAndDescription()
Set the Title and the description (Overwritten from ilObjectGUI, called by prepareOutput) ...
static stripSlashes($a_str, $a_strip_html=true, $a_allow="")
strip slashes if magic qoutes is enabled
static _getSavedTopics()
Get the stored topics from the user session.
Create styles array
The data for the language used.
static sendFailure($a_info="", $a_keep=false)
Send Failure Message to Screen.
saveObject()
Save the changed translations.
static _saveValues($a_lang_key, $a_values=array(), $a_remarks=array())
Save a set of translation in the database.
static ilTempnam($a_temp_path=null)
Create a temporary file in an ILIAS writable directory.
Create new PHPExcel object
obj_idprivate
addNewEntryObject(ilPropertyFormGUI $a_form=null)
static _getModules($a_lang_key)
Get all modules of a language.
maintainObject()
Process the language maintenance.
getId()
get the language object id (needed for filter serialization)
static replaceLangEntry( $a_module, $a_identifier, $a_lang_key, $a_value, $a_local_change=null, $a_remarks=null)
Replace lang entry.
if(!isset($_REQUEST['ReturnTo'])) if(!isset($_REQUEST['AuthId'])) $options