![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/mets.corals.io/wp-content/plugins/ultimate-blocks/src/blocks/review/ |
import icon from "./icon"; import { ReviewBody } from "./components"; import { version_1_1_2, version_1_1_4, version_1_1_5, updateFrom, } from "./oldVersions"; import { removeFromArray } from "../../common"; import { useEffect, useState } from "react"; import registerPluginBlock from "$Inc/registerPluginBlock"; const { __ } = wp.i18n; const { BlockControls, InspectorControls, PanelColorSettings, URLInput } = wp.blockEditor || wp.editor; const { ToolbarGroup, ToolbarButton, Button, ButtonGroup, FormToggle, PanelBody, PanelRow, RangeControl, RadioControl, SelectControl, TextControl, DatePicker, ToggleControl, } = wp.components; const { compose } = wp.compose; const { withSelect } = wp.data; const defaultAttributes = { ID: { type: "string", default: "", }, blockID: { type: "string", default: "", }, authorName: { type: "string", default: "", }, itemName: { type: "string", default: "", }, itemType: { type: "string", default: "Product", }, //for book, movie, and local business link itemPage: { type: "string", default: "", }, itemSubtype: { type: "string", default: "", }, itemSubsubtype: { type: "string", default: "", }, valueType: { type: "string", default: "star", //also support percent }, items: { type: "string", default: '[{"label":"","value":0}]', }, description: { type: "string", default: "", }, descriptionAlign: { type: "string", default: "left", }, imgPosition: { type: "string", default: "right", }, imgURL: { type: "string", default: "", }, imgID: { type: "number", }, imgAlt: { type: "string", default: "", }, parts: { type: "array", default: [{ label: "", value: 0 }], }, starCount: { type: "number", default: 5, }, useSummary: { type: "boolean", default: true, }, summaryTitle: { type: "string", default: "Summary", }, summaryDescription: { type: "string", default: "", }, callToActionText: { type: "string", default: "", }, callToActionFontSize: { type: "number", default: 0, }, callToActionURL: { type: "string", default: "", }, callToActionBackColor: { type: "string", default: "#f63d3d", }, callToActionBorderColor: { type: "string", default: "#ffffff", }, callToActionForeColor: { type: "string", default: "#ffffff", }, inactiveStarColor: { type: "string", default: "#888888", }, activeStarColor: { type: "string", default: "", }, activePercentBarColor: { type: "string", default: "", }, percentBarColor: { type: "string", default: "", }, titleAlign: { type: "string", default: "left", }, authorAlign: { type: "string", default: "left", }, enableCTA: { type: "boolean", default: true, }, ctaNoFollow: { type: "boolean", default: true, }, ctaOpenInNewTab: { type: "boolean", default: true, }, ctaIsSponsored: { type: "boolean", default: false, }, ctaAlignment: { type: "string", default: "left", }, enableReviewSchema: { type: "boolean", default: true, }, enableImage: { type: "boolean", default: false, }, enableDescription: { type: "boolean", default: false, }, starOutlineColor: { type: "string", default: "", }, imageSize: { type: "number", default: 100, //range: 0-200 }, brand: { type: "string", default: "", }, sku: { type: "string", default: "", }, identifier: { type: "string", default: "", }, identifierType: { type: "string", default: "gtin", // nsn, mpn, gtin8, gtin12, gtin13, gtin14, gtin }, offerType: { type: "string", default: "Offer", //can also be set to aggregate offer (which prevevnts calltoactionurl from being used as offer url) }, offerStatus: { type: "string", default: "InStock", //available values: Discontinued, InStock, InStoreOnly, LimitedAvailability, OnlineOnly, OutOfStock, PreOrder, PreSale, SoldOut }, //begin aggregate offer-only attributes offerHighPrice: { type: "number", default: 0, }, offerLowPrice: { type: "number", default: 0, }, offerCount: { type: "number", default: 0, }, //end aggregate offer-only attributes offerPrice: { //only for offer type: "number", default: 0, }, offerCurrency: { type: "string", default: "USD", }, offerExpiry: { type: "number", //default: 60 * (10080 + Math.ceil(Date.now() / 60000)), default: 0, }, usePhysicalAddress: { type: "boolean", default: true, //can be set to false when using event itemType }, address: { //for localbusiness location, organiztion location, and event location type: "string", default: "", }, addressName: { //for event location type: "string", default: "", }, url: { //for event and organization virtual location type: "string", default: "", }, reviewPublisher: { type: "string", default: "", }, reviewPublicationDate: { type: "number", default: Math.ceil(Date.now() / 1000), }, //beginning of book-only attributes bookAuthorName: { type: "string", default: "", }, isbn: { type: "string", default: "", }, //end of book-only attributes cuisines: { //for restaurant type: "array", default: [], //should be an array of strings }, phoneNumber: { type: "string", default: "", }, priceRange: { type: "string", default: "", }, appCategory: { //softwareapplication only type: "string", default: "", }, operatingSystem: { //softwareapplication only type: "string", default: "", }, provider: { //for course type: "string", default: "", }, //beginning of event-only attributes eventStartDate: { type: "number", default: 60 * (1440 + Math.ceil(Date.now() / 60000)), // 24 hours from Date.now }, eventEndDate: { type: "number", default: 0, //toggling an option should set this to 48 hours from Date.now }, eventPage: { type: "string", default: "", }, organizer: { type: "string", default: "", }, performer: { type: "string", default: "", }, //end event only attributes //begin video object attributes videoUploadDate: { type: "number", default: Math.ceil(Date.now() / 1000), }, videoURL: { type: "string", default: "", }, }; function ReviewMain(props) { const [editable, setEditable] = useState(""); const [editedStar, setEditedStar] = useState(0); const [lastCuisine, setLastCuisine] = useState(""); const [setEventEndDate, toggleSetEventEndDate] = useState(false); const [offerPriceRaw, setOfferPriceRaw] = useState("0"); const [offerHighPriceRaw, setOfferHighPriceRaw] = useState("0"); const [offerLowPriceRaw, setOfferLowPriceRaw] = useState("0"); const [isLoaded, setIsLoaded] = useState(false); const [setCTAFontSize, toggleSetCTAFontSize] = useState(false); const { attributes: { blockID, authorName, itemName, itemPage, itemType, itemSubtype, itemSubsubtype, description, imgPosition, imgID, imgAlt, imgURL, valueType, items, parts, starCount, useSummary, summaryTitle, summaryDescription, callToActionText, callToActionFontSize, callToActionURL, callToActionBackColor, callToActionBorderColor, callToActionForeColor, inactiveStarColor, activeStarColor, starOutlineColor, activePercentBarColor, percentBarColor, titleAlign, authorAlign, descriptionAlign, enableCTA, ctaNoFollow, ctaOpenInNewTab, ctaIsSponsored, ctaAlignment, enableReviewSchema, enableImage, enableDescription, imageSize, brand, sku, identifier, identifierType, offerType, offerCurrency, offerStatus, offerCount, offerExpiry, cuisines, appCategory, operatingSystem, provider, isbn, bookAuthorName, reviewPublisher, reviewPublicationDate, address, addressName, priceRange, phoneNumber, eventStartDate, eventEndDate, usePhysicalAddress, eventPage, organizer, performer, videoUploadDate, videoURL, }, setAttributes, isSelected, block, getBlock, getClientIdsWithDescendants, } = props; const setAlignment = (target, value) => { switch (target) { case "reviewTitle": setAttributes({ titleAlign: value }); break; case "reviewAuthor": setAttributes({ authorAlign: value }); break; case "reviewItemDescription": setAttributes({ descriptionAlign: value }); break; } }; const getCurrentAlignment = (target) => { switch (target) { case "reviewTitle": return titleAlign; case "reviewAuthor": return authorAlign; case "reviewItemDescription": return descriptionAlign; } }; useEffect(() => { const initialAttributes = {}; if (blockID === "") { Object.assign(initialAttributes, { blockID: block.clientId, starOutlineColor: "#f7b708", activeStarColor: "#f7b708", }); } else { if ( getClientIdsWithDescendants().some( (ID) => "blockID" in getBlock(ID).attributes && getBlock(ID).attributes.blockID === blockID ) ) { Object.assign(initialAttributes, { blockID: block.clientId }); } if (starOutlineColor === "") { Object.assign(initialAttributes, { starOutlineColor: "#000000", }); } if (activeStarColor === "") { Object.assign(initialAttributes, { activeStarColor: "#eeee00", }); } } setAttributes(initialAttributes); if ( items && items !== JSON.stringify(parts) && parts.length === 1 && parts[0].label === "" && parts[0].value === 0 ) { setAttributes({ parts: JSON.parse(items), items: '[{"label":"","value":0}]', }); } if (!isLoaded) { setIsLoaded(true); toggleSetCTAFontSize(callToActionFontSize > 0); } }, []); //begin dropdown setup let itemTypeExtras; const subtypeCategories = { Book: ["Audiobook"], Event: [ "BusinessEvent", "ChildrensEvent", "ComedyEvent", "CourseInstance", "DanceEvent", "DeliveryEvent", "EducationEvent", "EventSeries", //pending "Festival", "FoodEvent", "Hackathon", //pending "LiteraryEvent", "MusicEvent", "PublicationEvent", "SaleEvent", "ScreeningEvent", "SocialEvent", "SportsEvent", "TheaterEvent", "VisualArtsEvent", ], Game: ["VideoGame"], LocalBusiness: [ "AnimalShelter", "ArchiveOrganization", //pending "AutomotiveBusiness", "ChildCare", "Dentist", "DryCleaningOrLaundry", "EmergencyService", "EmploymentAgency", "EntertainmentBusiness", "FinancialService", "FoodEstablishment", "GovernmentOffice", "HealthAndBeautyBusiness", "HomeAndConstructionBusiness", "InternetCafe", "LegalService", "Library", "LodgingBusiness", "MedicalBusiness", "ProfessionalService", "RadioStation", "RealEstateAgent", "RecyclingCenter", "SelfStorage", "ShoppingCenter", "SportsActivityLocation", "TelevisionStation", "TouristInformationCenter", "TravelAgency", ], MediaObject: [ "3DModel", //pending "AudioObject", "DataDownload", "ImageObject", "LegislationObject", //pending "MusicVideoObject", "VideoObject", ], MusicPlaylist: ["MusicAlbum", "MusicRelease"], Organization: [ "Airline", "Consortium", //pending "Corporation", "EducationalOrganization", "FundingScheme", //pending "GovernmentOrganization", "LibrarySystem", //pending "MedicalOrganization", "NewsMediaOrganization", //pending "NGO", "PerformingGroup", "Project", //pending "SportsOrganization", "WorkersUnion", ], Product: [ "IndividualProduct", "ProductCollection", "ProductGroup", "ProductModel", "SomeProducts", "Vehicle", ], SoftwareApplication: ["MobileApplication", "VideoGame", "WebApplication"], }; const subsubtypes = { PublicationEvent: ["BroadcastEvent", "OnDemandEvent"], EducationalOrganization: [ "CollegeOrUniversity", "ElementarySchool", "HighSchool", "MiddleSchool", "Preschool", "School", ], MedicalOrganization: [ "Dentist", "DiagnosticLab", "Hospital", "MedicalClinic", "Pharmacy", "Physician", "VeterinaryCare", ], PerformingGroup: ["DanceGroup", "MusicGroup", "TheaterGroup"], Project: ["FundingAgency", "ResearchProject"], SportsOrganization: ["SportsTeam"], AutomotiveBusiness: [ "AutoBodyShop", "AutoDealer", "AutoPartsStore", "AutoRental", "AutoRepair", "AutoWash", "GasStation", "MotorcycleDealer", "MotorcycleRepair", ], EmergencyService: ["FireStation", "Hospital", "PoliceStation"], EntertainmentBusiness: [ "AdultEntertainment", "AmusementPark", "ArtGallery", "Casino", "ComedyClub", "MovieTheater", "NightClub", ], FinancialService: [ "AccountingService", "AutomatedTeller", "BankOrCreditUnion", "InsuranceAgency", ], FoodEstablishment: [ "Bakery", "BarOrPub", "Brewery", "CafeOrCoffeeShop", "Distillery", "FastFoodRestaurant", "IceCreamShop", "Restaurant", "Winery", ], GovernmentOffice: ["PostOffice"], HealthAndBeautyBusiness: [ "BeautySalon", "DaySpa", "HairSalon", "HealthClub", "NailSalon", "TattooParlor", ], HomeAndConstructionBusiness: [ "Electrician", "GeneralContractor", "HVACBusiness", "HousePainter", "Locksmith", "MovingCompany", "Plumber", "RoofingContractor", ], LegalService: ["Attorney", "Notary"], LodgingBusiness: [ "BedAndBreakfast", "Campground", "Hostel", "Hotel", "Motel", "Resort", ], MedicalBusiness: [ //only subtypes that support reviews are included "Dentist", "MedicalClinic", "Optician", "Pharmacy", "Physician", ], SportsActivityLocation: [ "BowlingAlley", "ExerciseGym", "GolfCourse", "HealthClub", "PublicSwimmingPool", "SkiResort", "SportsClub", "StadiumOrArena", "TennisComplex", ], Store: [ "AutoPartsStore", "BikeStore", "BookStore", "ClothingStore", "ComputerStore", "ConvenienceStore", "DepartmentStore", "ElectronicsStore", "Florist", "FurnitureStore", "GardenStore", "GroceryStore", "HardwareStore", "HobbyShop", "HomeGoodsStore", "JewelryStore", "LiquorStore", "MensClothingStore", "MobilePhoneStore", "MovieRentalStore", "MusicStore", "OfficeEquipmentStore", "OutletStore", "PawnShop", "PetStore", "ShoeStore", "SportingGoodsStore", "TireShop", "ToyStore", "WholesaleStore", ], }; const addressInput = ( <TextControl label={__("Address")} value={address} onChange={(address) => setAttributes({ address })} /> ); const cuisineInput = ( <> <p>{__("Serves cuisine")}</p> <ul className="ub_review_cuisine_list"> {Array.isArray(cuisines) && cuisines.length > 0 ? ( cuisines.map((c, i) => ( <li> {c} <span className="dashicons dashicons-dismiss" onClick={() => { setAttributes({ cuisines: [ ...cuisines.slice(0, i), ...cuisines.slice(i + 1), ], }); }} /> </li> )) ) : ( <span>{__("Cuisine list empty")}</span> )} </ul> <label>{__("Add a cuisine to the list")}</label> <input type="text" value={lastCuisine} onKeyUp={(e) => { if (e.key === "Enter" && e.target.value !== "") { setAttributes({ cuisines: [...cuisines, e.target.value], }); setLastCuisine(""); } }} onChange={(e) => { if (e.target.value.includes(",")) { const latestItemArray = e.target.value.split(","); if (latestItemArray[0] !== "") { setAttributes({ cuisines: [ ...(cuisines.length > 1 || cuisines[0] !== "" ? cuisines : []), ...latestItemArray.slice(0, latestItemArray.length - 1), ], }); setLastCuisine(latestItemArray[latestItemArray.length - 1]); } } else { setLastCuisine(e.target.value); } }} onBlur={() => { if (lastCuisine !== "") { setAttributes({ cuisines: [ ...(cuisines.length > 1 || cuisines[0] !== "" ? cuisines : []), lastCuisine, ], }); setLastCuisine(""); } }} /> </> ); const itemURLInput = ( <div id="ub_review_item_page_input"> <URLInput label={__(`${itemType} Page`)} autoFocus={false} value={itemPage} onChange={(itemPage) => setAttributes({ itemPage })} /> </div> ); const offerAttributes = [ "offerType", "offerStatus", "offerHighPrice", "offerLowPrice", "offerCount", "offerPrice", "offerCurrency", "offerExpiry", ]; let unusedDefaults = [ "bookAuthorName", "isbn", "provider", ...offerAttributes, "startDate", "endDate", "usePhysicalAddress", "addressName", "address", "eventPage", "itemPage", "organizer", "performer", "brand", "sku", "identifierType", "identifier", "cuisines", "phoneNumber", "priceRange", "appCategory", "operatingSystem", "videoUploadDate", "videoURL", ]; switch (itemType) { default: //there's nothing to add break; case "Book": itemTypeExtras = ( <> <TextControl label={__("ISBN")} value={isbn} onChange={(isbn) => setAttributes({ isbn })} /> <TextControl label={__("Book author name")} value={bookAuthorName} onChange={(bookAuthorName) => setAttributes({ bookAuthorName })} /> {itemURLInput} </> ); unusedDefaults = removeFromArray(unusedDefaults, [ "isbn", "bookAuthorName", "itemPage", ]); break; case "Course": itemTypeExtras = ( <TextControl label={__("Provider")} value={provider} onChange={(provider) => setAttributes({ provider })} /> ); unusedDefaults = removeFromArray(unusedDefaults, "provider"); break; case "Event": itemTypeExtras = ( <> <h3>{__("Event start date")}</h3> <DatePicker currentDate={eventStartDate * 1000} onChange={(newDate) => { const newDateVal = Math.floor(Date.parse(newDate) / 1000); setAttributes({ eventStartDate: newDateVal }); if (setEventEndDate && eventEndDate <= newDateVal) { setAttributes({ eventEndDate: 86400 + newDateVal, }); } }} /> <label htmlFor="ub-review-event-date-toggle"> {__("Use event end date")} </label> <FormToggle id="ub-review-event-date-toggle" label={__("Set event end date")} checked={setEventEndDate} onChange={() => { toggleSetEventEndDate(!setEventEndDate); setAttributes({ eventEndDate: setEventEndDate ? 0 : 86400 + eventStartDate, }); }} /> {setEventEndDate && [ <h3>{__("Event end date")}</h3>, <DatePicker currentDate={eventEndDate * 1000} onChange={(newDate) => setAttributes({ eventEndDate: Math.floor(Date.parse(newDate) / 1000), }) } />, ]} <PanelBody title={__("Event venue")} initialOpen> <Button icon="admin-home" isPrimary={usePhysicalAddress} onClick={() => setAttributes({ usePhysicalAddress: true })} showTooltip={true} label={"Use physical location"} /> <Button icon="admin-site-alt3" isPrimary={!usePhysicalAddress} onClick={() => setAttributes({ usePhysicalAddress: false })} showTooltip={true} label={"Use virtual location"} /> {usePhysicalAddress ? ( <> <TextControl label={__("Address Name")} value={addressName} onChange={(addressName) => setAttributes({ addressName })} /> {addressInput} </> ) : ( <div id="ub_review_event_page_input"> <URLInput label={__("Event Page")} autoFocus={false} value={eventPage} onChange={(eventPage) => setAttributes({ eventPage })} /> </div> )} </PanelBody> <TextControl label={__("Performer")} value={performer} onChange={(performer) => setAttributes({ performer })} /> <TextControl label={__("Organizer")} value={organizer} onChange={(organizer) => setAttributes({ organizer })} /> </> ); unusedDefaults = removeFromArray(unusedDefaults, [ ...offerAttributes, "startDate", "endDate", "usePhysicalAddress", "addressName", "address", "eventPage", "organizer", "performer", ]); break; case "Product": itemTypeExtras = ( <> <TextControl label={__("Brand")} value={brand} onChange={(brand) => setAttributes({ brand })} /> <TextControl label={__("SKU")} value={sku} onChange={(sku) => setAttributes({ sku })} /> <TextControl label={__("Identifier")} value={identifier} onChange={(identifier) => setAttributes({ identifier })} /> <SelectControl label={__("Identifier type")} value={identifierType} options={[ "nsn", "mpn", "gtin8", "gtin12", "gtin13", "gtin14", "gtin", ].map((a) => ({ label: __(a.toUpperCase()), value: a, }))} onChange={(identifierType) => setAttributes({ identifierType })} /> </> ); unusedDefaults = removeFromArray(unusedDefaults, [ "brand", "sku", "identifiertype", "identifier", ...offerAttributes, ]); break; case "LocalBusiness": itemTypeExtras = ( <> {itemSubtype === "FoodEstablishment" && itemSubsubtype !== "Distillery" && cuisineInput} {!( ["AnimalShelter", "ArchiveOrganization"].includes(itemSubtype) || ["FireStation", "PoliceStation"].includes(itemSubsubtype) ) && ( <TextControl label={__("Price Range")} value={priceRange} onChange={(priceRange) => setAttributes({ priceRange })} /> )} {addressInput} <TextControl label={__("Telephone Number")} type="tel" value={phoneNumber} onChange={(phoneNumber) => setAttributes({ phoneNumber })} /> {itemURLInput} </> ); if ( itemSubtype === "FoodEstablishment" && itemSubsubtype !== "Distillery" ) { unusedDefaults = removeFromArray(unusedDefaults, "cuisines"); } unusedDefaults = removeFromArray(unusedDefaults, [ "address", "itemPage", "phoneNumber", "priceRange", ]); break; case "Movie": itemTypeExtras = itemURLInput; unusedDefaults = removeFromArray(unusedDefaults, ["itemPage"]); break; case "Organization": itemTypeExtras = ( <> {(itemSubsubtype === "Hospital" || subsubtypes.MedicalBusiness.includes(itemSubsubtype)) && ( <TextControl label={__("Price Range")} value={priceRange} onChange={(priceRange) => setAttributes({ priceRange })} /> )} {addressInput} <TextControl label={__("Telephone Number")} type="tel" value={phoneNumber} onChange={(phoneNumber) => setAttributes({ phoneNumber })} /> </> ); unusedDefaults = removeFromArray(unusedDefaults, [ "address", "phoneNumber", "priceRange", ]); break; case "SoftwareApplication": itemTypeExtras = ( <> <TextControl label={__("Application Category")} value={appCategory} onChange={(appCategory) => setAttributes({ appCategory })} /> <TextControl label={__("Operating System")} value={operatingSystem} onChange={(operatingSystem) => setAttributes({ operatingSystem })} /> </> ); unusedDefaults = removeFromArray(unusedDefaults, [ ...offerAttributes, "appCategory", "operatingSystem", ]); break; case "MediaObject": if (itemSubtype === "VideoObject") { itemTypeExtras = ( <> <h3>{__("Video upload date")}</h3>, <DatePicker currentDate={videoUploadDate * 1000} onChange={(newDate) => setAttributes({ videoUploadDate: Math.floor(Date.parse(newDate) / 1000), }) } /> <div id="ub_review_video_url_input"> <URLInput label={__("Video URL")} autoFocus={false} value={videoURL} onChange={(videoURL) => setAttributes({ videoURL })} /> </div> </> ); unusedDefaults = removeFromArray(unusedDefaults, [ "videoUploadDate", "videoURL", ]); } break; } const schemaDefaults = Object.keys( Object.assign({}, defaultAttributes) ).reduce((defaults, attr) => { if (unusedDefaults.includes(attr)) { defaults[attr] = defaultAttributes[attr].default; } return defaults; }, {}); const unusedAttributes = Object.keys(props.attributes).reduce( (defaults, attr) => { if ( unusedDefaults.includes(attr) && props.attributes[attr] !== schemaDefaults[attr] ) { defaults[attr] = defaultAttributes[attr].default; } return defaults; }, {} ); if (Object.keys(unusedAttributes).length) { setAttributes(unusedAttributes); } const parser = new DOMParser(); return ( <> {isSelected && ( <InspectorControls> <PanelBody title={__("Review item rating format")}> <RadioControl selected={valueType} options={["star", "percent"].map((a) => ({ label: __(a), value: a, }))} onChange={(newValueType) => { const factor = 100 / starCount; setAttributes({ valueType: newValueType, parts: parts.map((p) => ({ label: p.label, value: valueType === "star" ? p.value * factor : p.value / factor, })), activePercentBarColor: valueType === "star" && !activePercentBarColor ? "#f63d3d" : activePercentBarColor, }); }} /> </PanelBody> <PanelBody title={__("Value settings")} initialOpen={false}> {editedStar > -1 && ( <RangeControl label={__( `Value for ${ parser.parseFromString(parts[editedStar].label, "text/html") .body.textContent || "current feature" }` )} value={parts[editedStar].value} onChange={(newValue) => { setAttributes({ parts: [ ...parts.slice(0, editedStar), Object.assign({}, parts[editedStar], { value: newValue, }), ...parts.slice(editedStar + 1), ], }); }} min={valueType === "star" ? 0 : 1} max={valueType === "star" ? starCount : 100} step={valueType === "star" ? 0.1 : 1} /> )} {valueType === "star" ? ( <PanelColorSettings title={__("Star Colors")} initialOpen={true} colorSettings={[ { value: activeStarColor, onChange: (colorValue) => setAttributes({ activeStarColor: colorValue }), label: __("Active Star Color"), }, { value: inactiveStarColor, onChange: (colorValue) => setAttributes({ inactiveStarColor: colorValue }), label: __("Inactive Star Color"), }, { value: starOutlineColor, onChange: (colorValue) => setAttributes({ starOutlineColor: colorValue }), label: __("Star Outline Color"), }, ]} /> ) : ( <PanelColorSettings title={__("Percentage Bar Colors")} colorSettings={[ { value: activePercentBarColor, onChange: (colorValue) => setAttributes({ activePercentBarColor: colorValue }), label: __("Main Color"), }, { value: percentBarColor, onChange: (colorValue) => setAttributes({ percentBarColor: colorValue }), label: __("Background Color"), }, ]} /> )} </PanelBody> <PanelColorSettings title={__("Button Colors")} initialOpen={false} colorSettings={[ { value: callToActionBackColor, onChange: (colorValue) => setAttributes({ callToActionBackColor: colorValue }), label: __("Button Background"), }, { value: callToActionBorderColor, onChange: (colorValue) => setAttributes({ callToActionBorderColor: colorValue }), label: __("Button Border Color"), }, { value: callToActionForeColor, onChange: (colorValue) => setAttributes({ callToActionForeColor: colorValue }), label: __("Button Text Color"), }, ]} /> <PanelBody title={__("Call to Action button")} initialOpen={true}> <PanelRow> <label htmlFor="ub-review-cta-enable">{__("Enable")}</label> <FormToggle id="ub-review-cta-enable" label={__("Enable")} checked={enableCTA} onChange={() => setAttributes({ enableCTA: !enableCTA })} /> </PanelRow> {enableCTA && ( <> <PanelRow> <label htmlFor="ub-review-cta-nofollow"> {__("Add nofollow")} </label> <FormToggle id="ub-review-cta-nofollow" label={__("Add nofollow")} checked={ctaNoFollow} onChange={() => setAttributes({ ctaNoFollow: !ctaNoFollow }) } /> </PanelRow> <PanelRow> <label htmlFor="ub-review-cta-openinnewtab"> {__("Open link in new tab")} </label> <FormToggle id="ub-review-cta-openinnewtab" label={__("Open link in new tab")} checked={ctaOpenInNewTab} onChange={() => setAttributes({ ctaOpenInNewTab: !ctaOpenInNewTab }) } /> </PanelRow> <PanelRow> <label htmlFor="ub-review-cta-issponsored"> {__("Mark link as sponsored")} </label> <FormToggle id="ub-review-cta-issponsored" label={__("Mark link as sponsored")} checked={ctaIsSponsored} onChange={() => setAttributes({ ctaIsSponsored: !ctaIsSponsored }) } /> </PanelRow> <PanelRow> <label>{__("Alignment")}</label> <ButtonGroup> {["left", "center", "right"].map((a) => ( <Button icon={`align-${a}`} isPrimary={ctaAlignment === a} onClick={() => setAttributes({ ctaAlignment: a })} /> ))} </ButtonGroup> </PanelRow> <PanelRow> <label htmlFor="ub-review-cta-changefontsize"> {__("Change font size")} </label> <FormToggle id="ub-review-cta-changefontsize" label={__("Change font size")} checked={setCTAFontSize} onChange={() => { toggleSetCTAFontSize(!setCTAFontSize); if (setCTAFontSize) { setAttributes({ callToActionFontSize: 0 }); } }} /> </PanelRow> {setCTAFontSize && ( <RangeControl label={__("Font size")} value={callToActionFontSize} onChange={(callToActionFontSize) => setAttributes({ callToActionFontSize }) } min={6} max={50} /> )} </> )} </PanelBody> <PanelBody title={__("Review schema")} initialOpen={true}> <PanelRow> <label htmlFor="ub-review-schema-toggle"> {__("Enable review schema")} </label> <FormToggle id="ub-review-schema-toggle" label={__("Enable review schema")} checked={enableReviewSchema} onChange={() => { let newAttributes = { enableReviewSchema: !enableReviewSchema, }; if (enableReviewSchema) { newAttributes = Object.assign(newAttributes, { enableImage: false, enableDescription: false, }); } setAttributes(newAttributes); }} /> </PanelRow> <PanelRow> <label htmlFor="ub-review-summary-toggle"> {__("Use review summary")} </label> <FormToggle id="ub-review-summary-toggle" label={__("Use review summary")} checked={useSummary} onChange={() => setAttributes({ useSummary: !useSummary })} /> </PanelRow> {enableReviewSchema && ( <> <SelectControl label={__("Item type")} value={itemType} onChange={(itemType) => { setAttributes({ itemType }); if (itemType === "Movie") { setAttributes({ enableImage: true }); } if (itemType === "Course") { setAttributes({ enableDescription: true }); } if ( !subtypeCategories.hasOwnProperty(itemType) || !subtypeCategories[itemType].includes(itemSubtype) ) { setAttributes({ itemSubtype: "", itemSubsubtype: "", }); } }} options={[ "Book", "Course", "CreativeWorkSeason", "CreativeWorkSeries", "Episode", "Event", "Game", "LocalBusiness", "MediaObject", "Movie", "MusicPlaylist", "MusicRecording", "Organization", "Product", "SoftwareApplication", ].map((a) => ({ label: a, value: a }))} /> {subtypeCategories.hasOwnProperty(itemType) && ( <SelectControl label={__("Item subtype")} value={itemSubtype} onChange={(itemSubtype) => { setAttributes({ itemSubtype }); if (itemSubtype === "VideoObject") { setAttributes({ enableImage: true }); } if ( !subsubtypes.hasOwnProperty(itemSubtype) || !subsubtypes[itemSubtype].includes(itemSubsubtype) ) { setAttributes({ itemSubsubtype: "" }); } }} options={["", ...subtypeCategories[itemType]].map((a) => ({ label: a, value: a, }))} /> )} {subsubtypes.hasOwnProperty(itemSubtype) && ( <SelectControl label={__("Item subsubtype")} value={itemSubsubtype} onChange={(itemSubsubtype) => setAttributes({ itemSubsubtype }) } options={["", ...subsubtypes[itemSubtype]].map((a) => ({ label: a, value: a, }))} /> )} </> )} <> {!( enableReviewSchema && (itemType === "Movie" || itemSubtype === "VideoObject") ) && ( //images are required for these item types and optional for the rest <PanelRow> <label htmlFor="ub-review-image-toggle"> {__("Enable review image")} </label> <FormToggle id="ub-review-image-toggle" label={__("Enable review image")} checked={enableImage} onChange={() => setAttributes({ enableImage: !enableImage }) } /> </PanelRow> )} {enableImage && ( <> <PanelRow> <label>{__("Image size")}</label> <input type="number" value={imageSize} onChange={(e) => setAttributes({ imageSize: Number(e.target.value) }) } /> </PanelRow> <PanelRow> <label>{__("Image position")}</label> <SelectControl value={imgPosition} onChange={(imgPosition) => setAttributes({ imgPosition })} options={[ "left", "right", ...(enableDescription ? ["top", "bottom"] : []), ].map((a) => ({ label: __(a), value: a, }))} /> </PanelRow> </> )} {(!enableReviewSchema || itemType !== "Course") && ( <PanelRow> <label htmlFor="ub-review-description-toggle"> {__("Enable review description")} </label> <FormToggle id="ub-review-description-toggle" label={__("Enable review description")} checked={enableDescription} onChange={() => { setAttributes({ enableDescription: !enableDescription }); if ( !enableDescription && ["top", "bottom"].includes(imgPosition) ) { setAttributes({ imgPosition: "right" }); } }} /> </PanelRow> )} </> {enableReviewSchema && ( <> {itemTypeExtras} <TextControl label={__("Review publisher")} value={reviewPublisher} onChange={(reviewPublisher) => setAttributes({ reviewPublisher }) } /> <p>{__("Review publication date")}</p> <DatePicker currentDate={reviewPublicationDate * 1000} onChange={(newDate) => setAttributes({ reviewPublicationDate: Math.floor( Date.parse(newDate) / 1000 ), }) } /> {["Event", "Product", "SoftwareApplication"].includes( itemType ) && ( <PanelBody title={__("Offer")}> <SelectControl label={__("Offer Type")} value={offerType} options={["Offer", "Aggregate Offer"].map((a) => ({ label: __(a), value: a.replace(" ", ""), }))} onChange={(offerType) => setAttributes({ offerType })} /> <TextControl label={__("Offer Currency")} value={offerCurrency} onChange={(offerCurrency) => setAttributes({ offerCurrency }) } /> {offerType === "Offer" ? ( <> <TextControl label={__("Offer Price")} value={offerPriceRaw} onChange={(val) => { if (!isNaN(Number(val))) { setAttributes({ offerPrice: Number(val) }); setOfferPriceRaw(val); } }} /> <SelectControl label={__("Offer Status")} value={offerStatus} options={[ "Discontinued", "In Stock", "In Store Only", "Limited Availability", "Online Only", "Out Of Stock", "Pre Order", "Pre Sale", "Sold Out", ].map((a) => ({ label: __(a), value: a.replace(" ", ""), }))} onChange={(offerStatus) => setAttributes({ offerStatus }) } /> <ToggleControl label={__("Offer expiration")} checked={offerExpiry > 0} onChange={() => setAttributes({ offerExpiry: offerExpiry ? 0 : 60 * (10080 + Math.ceil(Date.now() / 60000)), //default to one week from Date.now() when enabled }) } /> {offerExpiry > 0 && ( <DatePicker currentDate={offerExpiry * 1000} onChange={(newDate) => setAttributes({ offerExpiry: Math.floor( Date.parse(newDate) / 1000 ), }) } /> )} </> ) : ( <> <TextControl label={__("Offer Count")} value={offerCount} onChange={(val) => setAttributes({ offerCount: Number(val) }) } /> <TextControl label={__( `Lowest Available Price (${offerCurrency})` )} value={offerLowPriceRaw} onChange={(val) => { if (!isNaN(val)) { setOfferLowPriceRaw(val); setAttributes({ offerLowPrice: Number(val) }); } }} /> <TextControl label={__( `Highest Available Price (${offerCurrency})` )} value={offerHighPriceRaw} onChange={(val) => { if (!isNaN(val)) { setOfferHighPriceRaw(val); setAttributes({ offerHighPrice: Number(val) }); } }} /> </> )} </PanelBody> )} </> )} </PanelBody> </InspectorControls> )} {isSelected && ( <BlockControls> {editable !== "" && ( <ToolbarGroup> {["left", "center", "right", "justify"].map((a) => ( <ToolbarButton icon={`editor-${a === "justify" ? a : "align" + a}`} label={__( (a !== "justify" ? "Align " : "") + a[0].toUpperCase() + a.slice(1) )} isActive={getCurrentAlignment(editable) === a} onClick={() => setAlignment(editable, a)} /> ))} </ToolbarGroup> )} </BlockControls> )} <ReviewBody isSelected={isSelected} authorName={authorName} itemName={itemName} description={description} descriptionEnabled={enableDescription} blockID={blockID} imgID={imgID} imgAlt={imgAlt} imgURL={imgURL} imgPosition={imgPosition} enableImage={enableImage} valueType={valueType} parts={parts} starCount={starCount} useSummary={useSummary} summaryTitle={summaryTitle} summaryDescription={summaryDescription} callToActionText={callToActionText} callToActionURL={callToActionURL} callToActionBackColor={callToActionBackColor} callToActionBorderColor={callToActionBorderColor} callToActionForeColor={callToActionForeColor} ctaAlignment={ctaAlignment} inactiveStarColor={inactiveStarColor} activeStarColor={activeStarColor} activePercentBarColor={activePercentBarColor} percentBarColor={percentBarColor} selectedStarColor={activeStarColor} starOutlineColor={starOutlineColor} setAttributes={(newValues) => setAttributes(newValues)} setEditable={(val) => setEditable(val)} setActiveStarIndex={(val) => setEditedStar(val)} activeStarIndex={editedStar} alignments={{ titleAlign, authorAlign, descriptionAlign }} enableCTA={enableCTA} ctaNoFollow={ctaNoFollow} imageSize={imageSize} ctaFontSize={callToActionFontSize} measureCTAFontSize={setCTAFontSize} /> </> ); } registerPluginBlock("ub/review", { title: __("Review"), icon, category: "ultimateblocks", keywords: [__("Review"), __("Ultimate Blocks")], attributes: defaultAttributes, edit: compose([ withSelect((select, ownProps) => { const { getBlock, getClientIdsWithDescendants } = select("core/block-editor") || select("core/editor"); return { block: getBlock(ownProps.clientId), getBlock, getClientIdsWithDescendants, }; }), ])(ReviewMain), save: () => null, deprecated: [ updateFrom(version_1_1_2), updateFrom(version_1_1_4), updateFrom(version_1_1_5), ], });